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 
20 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
21 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
22 
23 import android.app.Application;
24 import android.content.Context;
25 import android.os.Bundle;
26 
27 import androidx.annotation.Nullable;
28 import androidx.fragment.app.FragmentTransaction;
29 import androidx.lifecycle.ViewModelProvider;
30 import androidx.preference.Preference;
31 import androidx.preference.PreferenceScreen;
32 
33 import com.android.permissioncontroller.R;
34 import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
35 import com.android.permissioncontroller.permission.model.livedatatypes.PermGroupPackagesUiInfo;
36 import com.android.permissioncontroller.permission.ui.UnusedAppsFragment;
37 import com.android.permissioncontroller.permission.ui.model.ManagePermissionsViewModel;
38 import com.android.permissioncontroller.permission.ui.model.ManageStandardPermissionsViewModel;
39 import com.android.permissioncontroller.permission.ui.model.PermissionGroupPreferenceUtils;
40 import com.android.permissioncontroller.permission.utils.Utils;
41 
42 import java.util.List;
43 
44 /**
45  * Shows the standard permissions that can be granted/denied.
46  *
47  * Largely based on the implementation of
48  * {@link com.android.permissioncontroller.permission.ui.television.ManagePermissionsFragment}.
49  */
50 public class AutoManageStandardPermissionsFragment extends AutoSettingsFrameFragment {
51 
52     private static final String KEY_OTHER_PERMISSIONS = "other_permissions";
53     private static final String KEY_AUTO_REVOKE = "auto_revoke_key";
54     private ManagePermissionsViewModel mManagePermissionsViewModel;
55     private ManageStandardPermissionsViewModel mManageStandardPermissionsViewModel;
56 
57     /** Returns a new instance of {@link AutoManageStandardPermissionsFragment}. */
newInstance()58     public static AutoManageStandardPermissionsFragment newInstance() {
59         return new AutoManageStandardPermissionsFragment();
60     }
61 
62     @Override
onCreate(@ullable Bundle savedInstanceState)63     public void onCreate(@Nullable Bundle savedInstanceState) {
64         super.onCreate(savedInstanceState);
65         setLoading(true);
66         setHeaderLabel(getString(R.string.app_permission_manager));
67 
68         final Application application = getActivity().getApplication();
69         final ViewModelProvider.Factory viewModelFactory =
70                 ViewModelProvider.AndroidViewModelFactory.getInstance(application);
71         mManagePermissionsViewModel = new ViewModelProvider(this, viewModelFactory)
72                 .get(ManagePermissionsViewModel.class);
73         mManageStandardPermissionsViewModel = new ViewModelProvider(this,
74                 ViewModelProvider.AndroidViewModelFactory.getInstance(application))
75                 .get(ManageStandardPermissionsViewModel.class);
76         mManagePermissionsViewModel.getUsedPermissionGroups().observe(this,
77                 this::onPermissionGroupsChanged);
78         mManageStandardPermissionsViewModel.getNumAutoRevoked().observe(this,
79                 this::onNumAutoRevokedChanged);
80     }
81 
82     @Override
onCreatePreferences(Bundle bundle, String s)83     public void onCreatePreferences(Bundle bundle, String s) {
84         setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
85     }
86 
onPermissionGroupsChanged(List<PermGroupPackagesUiInfo> permissionGroups)87     private void onPermissionGroupsChanged(List<PermGroupPackagesUiInfo> permissionGroups) {
88         final Context context = getPreferenceManager().getContext();
89         if (context == null) {
90             return;
91         }
92 
93         final PreferenceScreen screen = getPreferenceScreen();
94 
95         // First check if "Other preferences" button exists. If it does: remove it, but hold on to
96         // the reference - we'll it add back later, after the preferences for the permission groups
97         // have been updated. If it does not exist: create and hold on to it.
98         Preference otherPermissionsPreference = screen.findPreference(KEY_OTHER_PERMISSIONS);
99         if (otherPermissionsPreference == null) {
100             otherPermissionsPreference = buildOtherPermissionsPreference(context);
101         } else {
102             screen.removePreference(otherPermissionsPreference);
103             // The PreferenceScreen is ordering items as added
104             // (see PreferenceGroup#setOrderingAsAdded()), which means that it assigns positional
105             // indexes ("order") to Preferences incrementally as they are added, BUT ONLY IF their
106             // current "order" is the DEFAULT_ORDER.
107             // However, when the Preferences are removed from the group they keep their "order" and
108             // thus when they are re-added to a group (same or another) their "order" does not get
109             // re-assigned, so they may show up at the position they previously were at.
110             // We want the otherPermissionsPreference to always be the last in the list, so reset
111             // its "order" to DEFAULT, so that we add last to the group, it indeed goes into the
112             // last position.
113             otherPermissionsPreference.setOrder(Preference.DEFAULT_ORDER);
114         }
115 
116         PermissionGroupPreferenceUtils.updateGroupOfPermissionPreferences(context, screen,
117                 permissionGroups);
118 
119         screen.addPreference(otherPermissionsPreference);
120 
121         // load initial auto-revoke count, if it is ready
122         Integer numAutoRevoked = mManageStandardPermissionsViewModel.getNumAutoRevoked().getValue();
123         onNumAutoRevokedChanged(numAutoRevoked);
124 
125         setLoading(false);
126     }
127 
128 
buildOtherPermissionsPreference(Context context)129     private Preference buildOtherPermissionsPreference(Context context) {
130         final Preference preference = new Preference(context);
131         preference.setPersistent(false);
132         preference.setKey(KEY_OTHER_PERMISSIONS);
133         preference.setTitle(R.string.other_permissions_label);
134         preference.setIcon(
135                 Utils.applyTint(
136                         context, R.drawable.ic_more_items, android.R.attr.colorControlNormal));
137         preference.setOnPreferenceClickListener(p -> {
138             AutoManageOtherPermissionsFragment frag =
139                     new AutoManageOtherPermissionsFragment();
140             frag.setTargetFragment(AutoManageStandardPermissionsFragment.this,
141                     /* requestCode= */ 0);
142             FragmentTransaction ft = getFragmentManager().beginTransaction();
143             ft.replace(android.R.id.content, frag);
144             ft.addToBackStack(null);
145             ft.commit();
146             return true;
147         });
148         // Make invisible for now and subscribe to the LiveData that tracks whether there are any
149         // unused or additional permissions.
150         preference.setVisible(false);
151         mManagePermissionsViewModel.hasUnusedOrAdditionalPermissionGroups().observe(this,
152                 preference::setVisible);
153         return preference;
154     }
155 
onNumAutoRevokedChanged(Integer numAutoRevoked)156     private void onNumAutoRevokedChanged(Integer numAutoRevoked) {
157         // to prevent ui jank, don't display auto-revoke until categories have loaded
158         if (mManagePermissionsViewModel.getUsedPermissionGroups().getValue() == null) {
159             return;
160         }
161         Preference autoRevokePreference = getPreferenceScreen().findPreference(KEY_AUTO_REVOKE);
162         if (numAutoRevoked != null && numAutoRevoked != 0) {
163             if (autoRevokePreference == null) {
164                 autoRevokePreference = new Preference(getPreferenceManager().getContext());
165                 autoRevokePreference.setOrder(-1);
166                 autoRevokePreference.setKey(KEY_AUTO_REVOKE);
167                 autoRevokePreference.setSingleLineTitle(false);
168                 autoRevokePreference.setIcon(R.drawable.ic_info_outline);
169                 autoRevokePreference.setTitle(
170                         R.string.auto_revoke_permission_notification_title);
171                 autoRevokePreference.setSummary(
172                         R.string.auto_revoke_setting_subtitle);
173                 autoRevokePreference.setOnPreferenceClickListener(preference -> {
174                     AutoUnusedAppsFragment frag = AutoUnusedAppsFragment.newInstance();
175                     frag.setArguments(UnusedAppsFragment.createArgs(
176                             getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID)));
177                     frag.setTargetFragment(AutoManageStandardPermissionsFragment.this,
178                             /* requestCode= */ 0);
179                     FragmentTransaction ft = getFragmentManager().beginTransaction();
180                     ft.replace(android.R.id.content, frag);
181                     ft.addToBackStack(null);
182                     ft.commit();
183                     return true;
184                 });
185 
186                 getPreferenceScreen().addPreference(autoRevokePreference);
187             }
188         } else if (numAutoRevoked != null && autoRevokePreference != null) {
189             getPreferenceScreen().removePreference(autoRevokePreference);
190         }
191     }
192 }
193