• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.auto;
18 
19 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
20 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
21 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED;
22 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__ALLOWED;
23 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND;
24 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__DENIED;
25 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME;
26 
27 import static java.util.concurrent.TimeUnit.DAYS;
28 
29 import android.app.Activity;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.pm.PackageInfo;
33 import android.os.Build;
34 import android.os.Bundle;
35 import android.os.UserHandle;
36 import android.util.Log;
37 import android.widget.Toast;
38 
39 import androidx.annotation.NonNull;
40 import androidx.annotation.Nullable;
41 import androidx.annotation.RequiresApi;
42 import androidx.fragment.app.Fragment;
43 import androidx.lifecycle.ViewModelProvider;
44 import androidx.preference.Preference;
45 import androidx.preference.PreferenceCategory;
46 import androidx.preference.PreferenceGroup;
47 
48 import com.android.modules.utils.build.SdkLevel;
49 import com.android.permissioncontroller.PermissionControllerStatsLog;
50 import com.android.permissioncontroller.R;
51 import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
52 import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage;
53 import com.android.permissioncontroller.permission.model.v31.PermissionUsages;
54 import com.android.permissioncontroller.permission.ui.Category;
55 import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel;
56 import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModelFactory;
57 import com.android.permissioncontroller.permission.utils.KotlinUtils;
58 import com.android.permissioncontroller.permission.utils.StringUtils;
59 
60 import java.text.Collator;
61 import java.time.Instant;
62 import java.util.ArrayList;
63 import java.util.HashMap;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Random;
67 
68 /** Screen to show the permissions for a specific application. */
69 public class AutoAppPermissionsFragment extends AutoSettingsFrameFragment implements
70         PermissionUsages.PermissionsUsagesChangeCallback {
71     private static final String LOG_TAG = AutoAppPermissionsFragment.class.getSimpleName();
72 
73     private static final String IS_SYSTEM_PERMS_SCREEN = "_is_system_screen";
74     private static final String KEY_ALLOWED_PERMISSIONS_GROUP = Category.ALLOWED.getCategoryName();
75     private static final String KEY_DENIED_PERMISSIONS_GROUP = Category.DENIED.getCategoryName();
76 
77     private AppPermissionGroupsViewModel mViewModel;
78 
79     private String mPackageName;
80     private boolean mIsFirstLoad;
81     private UserHandle mUser;
82     private PermissionUsages mPermissionUsages;
83     private List<AppPermissionUsage> mAppPermissionUsages = new ArrayList<>();
84     private boolean mIsSystemPermsScreen;
85 
86     private Collator mCollator;
87 
88     /**
89      * @return A new fragment
90      */
newInstance(@onNull String packageName, @NonNull UserHandle userHandle, long sessionId, boolean isSystemPermsScreen)91     public static AutoAppPermissionsFragment newInstance(@NonNull String packageName,
92             @NonNull UserHandle userHandle, long sessionId, boolean isSystemPermsScreen) {
93         return setPackageNameAndUserHandle(new AutoAppPermissionsFragment(), packageName,
94                 userHandle, sessionId, isSystemPermsScreen);
95     }
96 
setPackageNameAndUserHandle(@onNull T fragment, @NonNull String packageName, @NonNull UserHandle userHandle, long sessionId, boolean isSystemPermsScreen)97     private static <T extends Fragment> T setPackageNameAndUserHandle(@NonNull T fragment,
98             @NonNull String packageName, @NonNull UserHandle userHandle, long sessionId,
99             boolean isSystemPermsScreen) {
100         Bundle arguments = new Bundle();
101         arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
102         arguments.putParcelable(Intent.EXTRA_USER, userHandle);
103         arguments.putLong(EXTRA_SESSION_ID, sessionId);
104         arguments.putBoolean(IS_SYSTEM_PERMS_SCREEN, isSystemPermsScreen);
105         fragment.setArguments(arguments);
106         return fragment;
107     }
108 
109     @Override
onCreate(Bundle savedInstanceState)110     public void onCreate(Bundle savedInstanceState) {
111         super.onCreate(savedInstanceState);
112         setLoading(true);
113 
114         mIsFirstLoad = true;
115         mPackageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
116         mUser = getArguments().getParcelable(Intent.EXTRA_USER);
117         mIsSystemPermsScreen = getArguments().getBoolean(IS_SYSTEM_PERMS_SCREEN, true);
118         UserHandle userHandle = getArguments().getParcelable(Intent.EXTRA_USER);
119         Activity activity = requireActivity();
120         PackageInfo packageInfo = AutoPermissionsUtils.getPackageInfo(activity, mPackageName,
121                 userHandle);
122         if (packageInfo == null) {
123             Toast.makeText(getContext(), R.string.app_not_found_dlg_title,
124                     Toast.LENGTH_LONG).show();
125             activity.finish();
126             return;
127         }
128 
129         mCollator = Collator.getInstance(
130                 getContext().getResources().getConfiguration().getLocales().get(0));
131         AppPermissionGroupsViewModelFactory factory =
132                 new AppPermissionGroupsViewModelFactory(mPackageName, userHandle,
133                         getArguments().getLong(EXTRA_SESSION_ID, 0));
134         mViewModel = new ViewModelProvider(this, factory).get(AppPermissionGroupsViewModel.class);
135 
136         setHeaderLabel(getContext().getString(R.string.app_permissions));
137         if (mIsSystemPermsScreen) {
138             setAction(getContext().getString(R.string.all_permissions), v -> showAllPermissions());
139         }
140         createPreferenceCategories(packageInfo);
141 
142         mViewModel.getPackagePermGroupsLiveData().observe(this, this::updatePreferences);
143         updatePreferences(mViewModel.getPackagePermGroupsLiveData().getValue());
144 
145         if (SdkLevel.isAtLeastS()) {
146             mPermissionUsages = new PermissionUsages(getContext());
147 
148             long aggregateDataFilterBeginDays =
149                     AppPermissionGroupsViewModel.AGGREGATE_DATA_FILTER_BEGIN_DAYS_1;
150 
151             long filterTimeBeginMillis = Math.max(System.currentTimeMillis()
152                             - DAYS.toMillis(aggregateDataFilterBeginDays),
153                     Instant.EPOCH.toEpochMilli());
154             mPermissionUsages.load(null, null, filterTimeBeginMillis, Long.MAX_VALUE,
155                     PermissionUsages.USAGE_FLAG_LAST, getActivity().getLoaderManager(),
156                     false, false, this, false);
157         }
158     }
159 
160     @Override
onCreatePreferences(Bundle bundle, String s)161     public void onCreatePreferences(Bundle bundle, String s) {
162         setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
163     }
164 
165 
166     @Override
167     @RequiresApi(Build.VERSION_CODES.S)
onPermissionUsagesChanged()168     public void onPermissionUsagesChanged() {
169         if (mPermissionUsages.getUsages().isEmpty()) {
170             return;
171         }
172         if (getContext() == null) {
173             // Async result has come in after our context is gone.
174             return;
175         }
176 
177         mAppPermissionUsages = new ArrayList<>(mPermissionUsages.getUsages());
178         updatePreferences(mViewModel.getPackagePermGroupsLiveData().getValue());
179     }
180 
showAllPermissions()181     private void showAllPermissions() {
182         Fragment frag = AutoAllAppPermissionsFragment.newInstance(
183                 getArguments().getString(Intent.EXTRA_PACKAGE_NAME),
184                 getArguments().getParcelable(Intent.EXTRA_USER),
185                 getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID));
186         getFragmentManager().beginTransaction()
187                 .replace(android.R.id.content, frag)
188                 .addToBackStack("AllPerms")
189                 .commit();
190     }
191 
bindUi(PackageInfo packageInfo)192     protected void bindUi(PackageInfo packageInfo) {
193         getPreferenceScreen().addPreference(
194                 AutoPermissionsUtils.createHeaderPreference(getContext(),
195                         packageInfo.applicationInfo));
196 
197         PreferenceGroup allowed = new PreferenceCategory(getContext());
198         allowed.setKey(KEY_ALLOWED_PERMISSIONS_GROUP);
199         allowed.setTitle(R.string.allowed_header);
200         getPreferenceScreen().addPreference(allowed);
201 
202         PreferenceGroup denied = new PreferenceCategory(getContext());
203         denied.setKey(KEY_DENIED_PERMISSIONS_GROUP);
204         denied.setTitle(R.string.denied_header);
205         getPreferenceScreen().addPreference(denied);
206     }
207 
createPreferenceCategories(PackageInfo packageInfo)208     private void createPreferenceCategories(PackageInfo packageInfo) {
209         bindUi(packageInfo);
210     }
211 
updatePreferences(@ullable Map<Category, List<AppPermissionGroupsViewModel.GroupUiInfo>> groupMap)212     private void updatePreferences(@Nullable
213             Map<Category, List<AppPermissionGroupsViewModel.GroupUiInfo>> groupMap) {
214         if (groupMap == null && mViewModel.getPackagePermGroupsLiveData().isInitialized()) {
215             // null because explicitly set to null
216             Toast.makeText(
217                     getActivity(), R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
218             Log.w(LOG_TAG, "invalid package " + mPackageName);
219 
220             getActivity().finish();
221             return;
222         } else if (groupMap == null) {
223             // null because uninitialized
224             return;
225         }
226 
227         Context context = getPreferenceManager().getContext();
228         if (context == null) {
229             return;
230         }
231 
232         Map<String, Long> groupUsageLastAccessTime = new HashMap<>();
233         mViewModel.extractGroupUsageLastAccessTime(groupUsageLastAccessTime, mAppPermissionUsages,
234                 mPackageName);
235 
236         for (Category grantCategory : groupMap.keySet()) {
237             if (Category.ASK.equals(grantCategory)) {
238                 // skip ask category for auto
239                 continue;
240             }
241             PreferenceCategory category = getPreferenceScreen().findPreference(
242                     grantCategory.getCategoryName());
243             if (grantCategory.equals(Category.ALLOWED_FOREGROUND)) {
244                 category = findPreference(Category.ALLOWED.getCategoryName());
245             }
246             int numExtraPerms = 0;
247 
248             category.removeAll();
249 
250 
251             for (AppPermissionGroupsViewModel.GroupUiInfo groupInfo : groupMap.get(grantCategory)) {
252                 if (groupInfo.isSystem() == mIsSystemPermsScreen) {
253                     Preference preference = createPermissionPreference(getContext(), groupInfo,
254                             groupUsageLastAccessTime);
255                     category.addPreference(preference);
256                 } else if (!groupInfo.isSystem()) {
257                     numExtraPerms++;
258                 }
259             }
260 
261 
262             if (numExtraPerms > 0) {
263                 setAdditionalPermissionsPreference(category, numExtraPerms, context);
264             }
265 
266             if (category.getPreferenceCount() == 0) {
267                 setNoPermissionPreference(category, grantCategory, context);
268             }
269 
270             KotlinUtils.INSTANCE.sortPreferenceGroup(category, this::comparePreferences, false);
271         }
272 
273 
274         if (mIsFirstLoad) {
275             logAppPermissionsFragmentView();
276             mIsFirstLoad = false;
277         }
278         setLoading(false);
279     }
280 
createPermissionPreference(Context context, AppPermissionGroupsViewModel.GroupUiInfo groupInfo, Map<String, Long> groupUsageLastAccessTime)281     private Preference createPermissionPreference(Context context,
282             AppPermissionGroupsViewModel.GroupUiInfo groupInfo,
283             Map<String, Long> groupUsageLastAccessTime) {
284         String groupName = groupInfo.getGroupName();
285         Preference preference = new Preference(context);
286         preference.setTitle(KotlinUtils.INSTANCE.getPermGroupLabel(context, groupName));
287         preference.setIcon(KotlinUtils.INSTANCE.getPermGroupIcon(context, groupName));
288         preference.setKey(groupName);
289         String summary = mViewModel.getPreferenceSummary(groupInfo, context,
290                 groupUsageLastAccessTime.get(groupName));
291         if (!summary.isEmpty()) {
292             preference.setSummary(summary);
293         }
294         preference.setOnPreferenceClickListener(pref -> {
295             Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSION);
296             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackageName);
297             intent.putExtra(Intent.EXTRA_PERMISSION_NAME, groupName);
298             intent.putExtra(Intent.EXTRA_USER, mUser);
299             intent.putExtra(EXTRA_CALLER_NAME, AutoAppPermissionsFragment.class.getName());
300             context.startActivity(intent);
301             return true;
302         });
303         return preference;
304     }
305 
setAdditionalPermissionsPreference(PreferenceCategory category, int numExtraPerms, Context context)306     private void setAdditionalPermissionsPreference(PreferenceCategory category, int numExtraPerms,
307             Context context) {
308         Preference extraPerms = new Preference(context);
309         extraPerms.setIcon(R.drawable.ic_toc);
310         extraPerms.setTitle(R.string.additional_permissions);
311         extraPerms.setOnPreferenceClickListener(preference -> {
312             AutoAppPermissionsFragment
313                     frag = AutoAppPermissionsFragment.newInstance(mPackageName, mUser,
314                     getArguments().getLong(EXTRA_SESSION_ID), false);
315             frag.setTargetFragment(AutoAppPermissionsFragment.this, 0);
316             getFragmentManager().beginTransaction()
317                     .replace(android.R.id.content, frag)
318                     .addToBackStack(null)
319                     .commit();
320             return true;
321         });
322         extraPerms.setSummary(StringUtils.getIcuPluralsString(getContext(),
323                 R.string.additional_permissions_more, numExtraPerms));
324         category.addPreference(extraPerms);
325     }
326 
setNoPermissionPreference(PreferenceCategory category, Category grantCategory, Context context)327     private void setNoPermissionPreference(PreferenceCategory category, Category grantCategory,
328             Context context) {
329         Preference empty = new Preference(context);
330         empty.setKey(getString(grantCategory.equals(Category.DENIED)
331                 ? R.string.no_permissions_denied : R.string.no_permissions_allowed));
332         empty.setTitle(empty.getKey());
333         empty.setSelectable(false);
334         category.addPreference(empty);
335     }
336 
comparePreferences(Preference lhs, Preference rhs)337     private int comparePreferences(Preference lhs, Preference rhs) {
338         String additionalTitle = lhs.getContext().getString(R.string.additional_permissions);
339         if (lhs.getTitle().equals(additionalTitle)) {
340             return 1;
341         } else if (rhs.getTitle().equals(additionalTitle)) {
342             return -1;
343         }
344         return mCollator.compare(lhs.getTitle().toString(),
345                 rhs.getTitle().toString());
346     }
347 
logAppPermissionsFragmentView()348     private void logAppPermissionsFragmentView() {
349         Context context = getPreferenceManager().getContext();
350         if (context == null) {
351             return;
352         }
353         String permissionSubtitleOnlyInForeground =
354                 context.getString(R.string.permission_subtitle_only_in_foreground);
355 
356 
357         long sessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID);
358         long viewId = new Random().nextLong();
359 
360         PreferenceCategory allowed = findPreference(KEY_ALLOWED_PERMISSIONS_GROUP);
361 
362         int numAllowed = allowed.getPreferenceCount();
363         for (int i = 0; i < numAllowed; i++) {
364             Preference preference = allowed.getPreference(i);
365             if (preference.getTitle().equals(getString(R.string.no_permissions_allowed))) {
366                 // R.string.no_permission_allowed was added to PreferenceCategory
367                 continue;
368             }
369 
370             int category = APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__ALLOWED;
371             if (preference.getSummary() != null
372                     && permissionSubtitleOnlyInForeground.contentEquals(preference.getSummary())) {
373                 category = APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND;
374             }
375 
376             logAppPermissionsFragmentViewEntry(sessionId, viewId, preference.getKey(), category);
377         }
378 
379         PreferenceCategory denied = findPreference(KEY_DENIED_PERMISSIONS_GROUP);
380 
381         int numDenied = denied.getPreferenceCount();
382         for (int i = 0; i < numDenied; i++) {
383             Preference preference = denied.getPreference(i);
384             if (preference.getTitle().equals(getString(R.string.no_permissions_denied))) {
385                 // R.string.no_permission_denied was added to PreferenceCategory
386                 continue;
387             }
388             logAppPermissionsFragmentViewEntry(sessionId, viewId, preference.getKey(),
389                     APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__DENIED);
390         }
391     }
392 
logAppPermissionsFragmentViewEntry( long sessionId, long viewId, String permissionGroupName, int category)393     private void logAppPermissionsFragmentViewEntry(
394             long sessionId, long viewId, String permissionGroupName, int category) {
395         Integer uid = KotlinUtils.INSTANCE.getPackageUid(getActivity().getApplication(),
396                 mPackageName, mUser);
397         if (uid == null) {
398             return;
399         }
400         PermissionControllerStatsLog.write(APP_PERMISSIONS_FRAGMENT_VIEWED, sessionId, viewId,
401                 permissionGroupName, uid, mPackageName, category);
402         Log.i(LOG_TAG, "AutoAppPermissionFragment view logged with sessionId=" + sessionId
403                 + " viewId=" + viewId + " permissionGroupName=" + permissionGroupName + " uid="
404                 + uid + " packageName="
405                 + mPackageName + " category=" + category);
406     }
407 }
408 
409