1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.permissioncontroller.permission.ui.wear
18 
19 import android.app.Activity
20 import android.content.Intent
21 import android.content.pm.PackageInfo
22 import android.content.pm.PackageManager
23 import android.os.Build
24 import android.os.Bundle
25 import android.os.UserHandle
26 import android.util.Log
27 import android.view.LayoutInflater
28 import android.view.View
29 import android.view.ViewGroup
30 import android.widget.Toast
31 import androidx.annotation.RequiresApi
32 import androidx.compose.ui.platform.ComposeView
33 import androidx.core.os.BundleCompat
34 import androidx.fragment.app.Fragment
35 import androidx.lifecycle.ViewModelProvider
36 import com.android.modules.utils.build.SdkLevel
37 import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
38 import com.android.permissioncontroller.R
39 import com.android.permissioncontroller.permission.model.AppPermissions
40 import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage
41 import com.android.permissioncontroller.permission.model.v31.PermissionUsages
42 import com.android.permissioncontroller.permission.model.v31.PermissionUsages.PermissionsUsagesChangeCallback
43 import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel
44 import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModelFactory
45 import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionGroupsRevokeDialogViewModel
46 import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionGroupsRevokeDialogViewModelFactory
47 import com.android.permissioncontroller.permission.ui.wear.model.WearAppPermissionUsagesViewModel
48 import com.android.permissioncontroller.permission.ui.wear.model.WearAppPermissionUsagesViewModelFactory
49 import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme
50 import com.android.permissioncontroller.permission.utils.KotlinUtils.is7DayToggleEnabled
51 import java.time.Instant
52 import java.util.concurrent.TimeUnit
53 
54 class WearAppPermissionGroupsFragment : Fragment(), PermissionsUsagesChangeCallback {
55     private lateinit var permissionUsages: PermissionUsages
56     private lateinit var wearViewModel: WearAppPermissionUsagesViewModel
57     private lateinit var helper: WearAppPermissionGroupsHelper
58 
59     // Suppress warning of the deprecated class [android.app.LoaderManager] since other form factors
60     // are using the class to load PermissionUsages.
61     @Suppress("DEPRECATION")
onCreateViewnull62     override fun onCreateView(
63         inflater: LayoutInflater,
64         container: ViewGroup?,
65         savedInstanceState: Bundle?
66     ): View? {
67         val packageName = arguments?.getString(Intent.EXTRA_PACKAGE_NAME) ?: ""
68         val user =
69             arguments?.let {
70                 BundleCompat.getParcelable(it, Intent.EXTRA_USER, UserHandle::class.java)!!
71             }
72                 ?: UserHandle.SYSTEM
73 
74         val activity: Activity = requireActivity()
75         val packageManager = activity.packageManager
76 
77         val packageInfo: PackageInfo? =
78             try {
79                 packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS)
80             } catch (e: PackageManager.NameNotFoundException) {
81                 Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e)
82                 null
83             }
84 
85         if (packageInfo == null) {
86             Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show()
87             activity.finish()
88             return null
89         }
90         val sessionId = arguments?.getLong(EXTRA_SESSION_ID, 0) ?: 0
91         val appPermissions = AppPermissions(activity, packageInfo, true, { activity.finish() })
92         val factory = AppPermissionGroupsViewModelFactory(packageName, user, sessionId)
93         val viewModel =
94             ViewModelProvider(this, factory).get(AppPermissionGroupsViewModel::class.java)
95         wearViewModel =
96             ViewModelProvider(this, WearAppPermissionUsagesViewModelFactory())
97                 .get(WearAppPermissionUsagesViewModel::class.java)
98         val revokeDialogViewModel =
99             ViewModelProvider(this, AppPermissionGroupsRevokeDialogViewModelFactory())
100                 .get(AppPermissionGroupsRevokeDialogViewModel::class.java)
101 
102         val context = requireContext()
103 
104         // If the build type is below S, the app ops for permission usage can't be found. Thus, we
105         // shouldn't load permission usages, for them.
106         if (SdkLevel.isAtLeastS()) {
107             permissionUsages = PermissionUsages(context)
108             val aggregateDataFilterBeginDays =
109                 (if (is7DayToggleEnabled())
110                         AppPermissionGroupsViewModel.AGGREGATE_DATA_FILTER_BEGIN_DAYS_7
111                     else AppPermissionGroupsViewModel.AGGREGATE_DATA_FILTER_BEGIN_DAYS_1)
112                     .toLong()
113 
114             val filterTimeBeginMillis =
115                 Math.max(
116                     System.currentTimeMillis() -
117                         TimeUnit.DAYS.toMillis(aggregateDataFilterBeginDays),
118                     Instant.EPOCH.toEpochMilli()
119                 )
120             permissionUsages.load(
121                 null,
122                 null,
123                 filterTimeBeginMillis,
124                 Long.MAX_VALUE,
125                 PermissionUsages.USAGE_FLAG_LAST,
126                 requireActivity().getLoaderManager(),
127                 false,
128                 false,
129                 this,
130                 false
131             )
132         }
133         helper =
134             WearAppPermissionGroupsHelper(
135                 context = context,
136                 fragment = this,
137                 user = user,
138                 packageName = packageName,
139                 sessionId = sessionId,
140                 appPermissions = appPermissions,
141                 viewModel = viewModel,
142                 wearViewModel = wearViewModel,
143                 revokeDialogViewModel = revokeDialogViewModel
144             )
145 
146         return ComposeView(activity).apply {
147             setContent { WearPermissionTheme { WearAppPermissionGroupsScreen(helper) } }
148         }
149     }
150 
onPausenull151     override fun onPause() {
152         super.onPause()
153         helper.logAndClearToggledGroups()
154     }
155 
156     @RequiresApi(Build.VERSION_CODES.S)
onPermissionUsagesChangednull157     override fun onPermissionUsagesChanged() {
158         if (permissionUsages.usages.isEmpty()) {
159             return
160         }
161         if (getContext() == null) {
162             // Async result has come in after our context is gone.
163             return
164         }
165         wearViewModel.appPermissionUsages.value =
166             ArrayList<AppPermissionUsage>(permissionUsages.usages)
167     }
168 
169     companion object {
170         const val LOG_TAG = "WearAppPermissionGroups"
171     }
172 }
173