• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 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.car.settings.applications.specialaccess;
18 
19 import android.app.AppOpsManager;
20 import android.car.drivingstate.CarUxRestrictions;
21 import android.content.Context;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageManager;
24 
25 import androidx.annotation.CallSuper;
26 import androidx.annotation.VisibleForTesting;
27 import androidx.preference.Preference;
28 import androidx.preference.PreferenceGroup;
29 
30 import com.android.car.settings.R;
31 import com.android.car.settings.applications.specialaccess.AppStateAppOpsBridge.PermissionState;
32 import com.android.car.settings.common.FragmentController;
33 import com.android.car.settings.common.PreferenceController;
34 import com.android.car.ui.preference.CarUiSwitchPreference;
35 import com.android.settingslib.applications.ApplicationsState;
36 import com.android.settingslib.applications.ApplicationsState.AppEntry;
37 import com.android.settingslib.applications.ApplicationsState.AppFilter;
38 import com.android.settingslib.applications.ApplicationsState.CompoundFilter;
39 
40 import java.util.List;
41 
42 /**
43  * Displays a list of toggles for applications requesting permission to perform the operation with
44  * which this controller was initialized. {@link #init(int, String, int)} should be called when
45  * this controller is instantiated to specify the {@link AppOpsManager} operation code to control
46  * access for.
47  */
48 public class AppOpsPreferenceController extends PreferenceController<PreferenceGroup> {
49 
50     private static final AppFilter FILTER_HAS_INFO = new AppFilter() {
51         @Override
52         public void init() {
53             // No op.
54         }
55 
56         @Override
57         public boolean filterApp(AppEntry info) {
58             return info.extraInfo != null;
59         }
60     };
61 
62     private final AppOpsManager mAppOpsManager;
63 
64     private final Preference.OnPreferenceChangeListener mOnPreferenceChangeListener =
65             new Preference.OnPreferenceChangeListener() {
66                 @Override
67                 public boolean onPreferenceChange(Preference preference, Object newValue) {
68                     AppOpPreference appOpPreference = (AppOpPreference) preference;
69                     AppEntry entry = appOpPreference.mEntry;
70                     PermissionState extraInfo = (PermissionState) entry.extraInfo;
71                     boolean allowOp = (Boolean) newValue;
72                     if (allowOp != extraInfo.isPermissible()) {
73                         mAppOpsManager.setMode(mAppOpsOpCode, entry.info.uid,
74                                 entry.info.packageName,
75                                 allowOp ? AppOpsManager.MODE_ALLOWED : mNegativeOpMode);
76                         // Update the extra info of this entry so that it reflects the new mode.
77                         mAppEntryListManager.forceUpdate(entry);
78                         return true;
79                     }
80                     return false;
81                 }
82             };
83 
84     private final AppEntryListManager.Callback mCallback = new AppEntryListManager.Callback() {
85         @Override
86         public void onAppEntryListChanged(List<AppEntry> entries) {
87             mEntries = entries;
88             refreshUi();
89         }
90     };
91 
92     private int mAppOpsOpCode = AppOpsManager.OP_NONE;
93     private String mPermission;
94     private int mNegativeOpMode = -1;
95 
96     @VisibleForTesting
97     AppEntryListManager mAppEntryListManager;
98     private List<AppEntry> mEntries;
99 
100     private boolean mShowSystem;
101 
AppOpsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)102     public AppOpsPreferenceController(Context context, String preferenceKey,
103             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
104         this(context, preferenceKey, fragmentController, uxRestrictions,
105                 context.getSystemService(AppOpsManager.class));
106     }
107 
108     @VisibleForTesting
AppOpsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions, AppOpsManager appOpsManager)109     AppOpsPreferenceController(Context context, String preferenceKey,
110             FragmentController fragmentController, CarUxRestrictions uxRestrictions,
111             AppOpsManager appOpsManager) {
112         super(context, preferenceKey, fragmentController, uxRestrictions);
113         mAppOpsManager = appOpsManager;
114         mAppEntryListManager = new AppEntryListManager(context);
115     }
116 
117     @Override
getPreferenceType()118     protected Class<PreferenceGroup> getPreferenceType() {
119         return PreferenceGroup.class;
120     }
121 
122     /**
123      * Initializes this controller with the {@code appOpsOpCode} (selected from the operations in
124      * {@link AppOpsManager}) to control access for.
125      *
126      * @param permission     the {@link android.Manifest.permission} apps must hold to perform the
127      *                       operation.
128      * @param negativeOpMode the operation mode that will be passed to {@link
129      *                       AppOpsManager#setMode(int, int, String, int)} when access for a app is
130      *                       revoked.
131      */
init(int appOpsOpCode, String permission, int negativeOpMode)132     public void init(int appOpsOpCode, String permission, int negativeOpMode) {
133         mAppOpsOpCode = appOpsOpCode;
134         mPermission = permission;
135         mNegativeOpMode = negativeOpMode;
136     }
137 
138     /**
139      * Rebuilds the preference list to show system applications if {@code showSystem} is true.
140      * System applications will be hidden otherwise.
141      */
setShowSystem(boolean showSystem)142     public void setShowSystem(boolean showSystem) {
143         if (mShowSystem != showSystem) {
144             mShowSystem = showSystem;
145             mAppEntryListManager.forceUpdate();
146         }
147     }
148 
149     @Override
checkInitialized()150     protected void checkInitialized() {
151         if (mAppOpsOpCode == AppOpsManager.OP_NONE) {
152             throw new IllegalStateException("App operation code must be initialized");
153         }
154         if (mPermission == null) {
155             throw new IllegalStateException("Manifest permission must be initialized");
156         }
157         if (mNegativeOpMode == -1) {
158             throw new IllegalStateException("Negative case app operation mode must be initialized");
159         }
160     }
161 
162     @Override
onCreateInternal()163     protected void onCreateInternal() {
164         AppStateAppOpsBridge extraInfoBridge = new AppStateAppOpsBridge(getContext(), mAppOpsOpCode,
165                 mPermission);
166         mAppEntryListManager.init(extraInfoBridge, this::getAppFilter, mCallback);
167     }
168 
169     @Override
onStartInternal()170     protected void onStartInternal() {
171         mAppEntryListManager.start();
172     }
173 
174     @Override
onStopInternal()175     protected void onStopInternal() {
176         mAppEntryListManager.stop();
177     }
178 
179     @Override
onDestroyInternal()180     protected void onDestroyInternal() {
181         mAppEntryListManager.destroy();
182     }
183 
184     @Override
updateState(PreferenceGroup preference)185     protected void updateState(PreferenceGroup preference) {
186         if (mEntries == null) {
187             // Still loading.
188             return;
189         }
190         preference.removeAll();
191         for (AppEntry entry : mEntries) {
192             Preference appOpPreference = new AppOpPreference(getContext(), entry);
193             appOpPreference.setOnPreferenceChangeListener(mOnPreferenceChangeListener);
194             preference.addPreference(appOpPreference);
195         }
196     }
197 
198     @CallSuper
getAppFilter()199     protected AppFilter getAppFilter() {
200         AppFilter filterObj = new CompoundFilter(FILTER_HAS_INFO,
201                 ApplicationsState.FILTER_NOT_HIDE);
202         if (!mShowSystem) {
203             filterObj = new CompoundFilter(filterObj,
204                     ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER);
205         }
206         return filterObj;
207     }
208 
209     private static class AppOpPreference extends CarUiSwitchPreference {
210 
211         private final AppEntry mEntry;
212 
AppOpPreference(Context context, AppEntry entry)213         AppOpPreference(Context context, AppEntry entry) {
214             super(context);
215             String key = entry.info.packageName + "|" + entry.info.uid;
216             setKey(key);
217             setTitle(entry.label);
218             setIcon(entry.icon);
219             setSummary(getAppStateText(entry.info));
220             setPersistent(false);
221             PermissionState extraInfo = (PermissionState) entry.extraInfo;
222             setChecked(extraInfo.isPermissible());
223             mEntry = entry;
224         }
225 
getAppStateText(ApplicationInfo info)226         private String getAppStateText(ApplicationInfo info) {
227             if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
228                 return getContext().getString(R.string.not_installed);
229             } else if (!info.enabled || info.enabledSetting
230                     == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
231                 return getContext().getString(R.string.disabled);
232             }
233             return null;
234         }
235     }
236 }
237