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 super(context, preferenceKey, fragmentController, uxRestrictions); 105 mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); 106 mAppEntryListManager = new AppEntryListManager(context); 107 } 108 109 @Override getPreferenceType()110 protected Class<PreferenceGroup> getPreferenceType() { 111 return PreferenceGroup.class; 112 } 113 114 /** 115 * Initializes this controller with the {@code appOpsOpCode} (selected from the operations in 116 * {@link AppOpsManager}) to control access for. 117 * 118 * @param permission the {@link android.Manifest.permission} apps must hold to perform the 119 * operation. 120 * @param negativeOpMode the operation mode that will be passed to {@link 121 * AppOpsManager#setMode(int, int, String, int)} when access for a app is 122 * revoked. 123 */ init(int appOpsOpCode, String permission, int negativeOpMode)124 public void init(int appOpsOpCode, String permission, int negativeOpMode) { 125 mAppOpsOpCode = appOpsOpCode; 126 mPermission = permission; 127 mNegativeOpMode = negativeOpMode; 128 } 129 130 /** 131 * Rebuilds the preference list to show system applications if {@code showSystem} is true. 132 * System applications will be hidden otherwise. 133 */ setShowSystem(boolean showSystem)134 public void setShowSystem(boolean showSystem) { 135 if (mShowSystem != showSystem) { 136 mShowSystem = showSystem; 137 mAppEntryListManager.forceUpdate(); 138 } 139 } 140 141 @Override checkInitialized()142 protected void checkInitialized() { 143 if (mAppOpsOpCode == AppOpsManager.OP_NONE) { 144 throw new IllegalStateException("App operation code must be initialized"); 145 } 146 if (mPermission == null) { 147 throw new IllegalStateException("Manifest permission must be initialized"); 148 } 149 if (mNegativeOpMode == -1) { 150 throw new IllegalStateException("Negative case app operation mode must be initialized"); 151 } 152 } 153 154 @Override onCreateInternal()155 protected void onCreateInternal() { 156 AppStateAppOpsBridge extraInfoBridge = new AppStateAppOpsBridge(getContext(), mAppOpsOpCode, 157 mPermission); 158 mAppEntryListManager.init(extraInfoBridge, this::getAppFilter, mCallback); 159 } 160 161 @Override onStartInternal()162 protected void onStartInternal() { 163 mAppEntryListManager.start(); 164 } 165 166 @Override onStopInternal()167 protected void onStopInternal() { 168 mAppEntryListManager.stop(); 169 } 170 171 @Override onDestroyInternal()172 protected void onDestroyInternal() { 173 mAppEntryListManager.destroy(); 174 } 175 176 @Override updateState(PreferenceGroup preference)177 protected void updateState(PreferenceGroup preference) { 178 if (mEntries == null) { 179 // Still loading. 180 return; 181 } 182 preference.removeAll(); 183 for (AppEntry entry : mEntries) { 184 Preference appOpPreference = new AppOpPreference(getContext(), entry); 185 appOpPreference.setOnPreferenceChangeListener(mOnPreferenceChangeListener); 186 preference.addPreference(appOpPreference); 187 } 188 } 189 190 @CallSuper getAppFilter()191 protected AppFilter getAppFilter() { 192 AppFilter filterObj = new CompoundFilter(FILTER_HAS_INFO, 193 ApplicationsState.FILTER_NOT_HIDE); 194 if (!mShowSystem) { 195 filterObj = new CompoundFilter(filterObj, 196 ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER); 197 } 198 return filterObj; 199 } 200 201 private static class AppOpPreference extends CarUiSwitchPreference { 202 203 private final AppEntry mEntry; 204 AppOpPreference(Context context, AppEntry entry)205 AppOpPreference(Context context, AppEntry entry) { 206 super(context); 207 String key = entry.info.packageName + "|" + entry.info.uid; 208 setKey(key); 209 setTitle(entry.label); 210 setIcon(entry.icon); 211 setSummary(getAppStateText(entry.info)); 212 setPersistent(false); 213 PermissionState extraInfo = (PermissionState) entry.extraInfo; 214 setChecked(extraInfo.isPermissible()); 215 mEntry = entry; 216 } 217 getAppStateText(ApplicationInfo info)218 private String getAppStateText(ApplicationInfo info) { 219 if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { 220 return getContext().getString(R.string.not_installed); 221 } else if (!info.enabled || info.enabledSetting 222 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) { 223 return getContext().getString(R.string.disabled); 224 } 225 return null; 226 } 227 } 228 } 229