1 /* 2 * Copyright (C) 2017 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.tv.settings.device.apps.specialaccess; 18 19 import android.app.ActivityThread; 20 import android.app.AppOpsManager; 21 import android.content.Context; 22 import android.content.pm.IPackageManager; 23 import android.content.pm.PackageManager; 24 import android.os.Bundle; 25 import android.os.RemoteException; 26 import android.util.Log; 27 28 import androidx.annotation.NonNull; 29 import androidx.annotation.Nullable; 30 import androidx.preference.Preference; 31 32 import com.android.internal.util.ArrayUtils; 33 import com.android.settingslib.applications.ApplicationsState; 34 import com.android.tv.settings.R; 35 import com.android.tv.settings.SettingsPreferenceFragment; 36 37 import java.util.Arrays; 38 import java.util.Comparator; 39 import java.util.Set; 40 import java.util.stream.Collectors; 41 42 /** 43 * Base class for managing app ops 44 */ 45 public abstract class ManageAppOp extends SettingsPreferenceFragment 46 implements ManageApplicationsController.Callback { 47 private static final String TAG = "ManageAppOps"; 48 49 private IPackageManager mIPackageManager; 50 private AppOpsManager mAppOpsManager; 51 52 private ManageApplicationsController mManageApplicationsController; 53 54 @Override onAttach(Context context)55 public void onAttach(Context context) { 56 super.onAttach(context); 57 mManageApplicationsController = new ManageApplicationsController(context, this, 58 getLifecycle(), getAppFilter(), getAppComparator()); 59 } 60 61 @Override onCreate(Bundle savedInstanceState)62 public void onCreate(Bundle savedInstanceState) { 63 mIPackageManager = ActivityThread.getPackageManager(); 64 mAppOpsManager = getContext().getSystemService(AppOpsManager.class); 65 super.onCreate(savedInstanceState); 66 } 67 68 /** 69 * Subclasses may override this to provide an alternate app filter. The default filter inserts 70 * {@link PermissionState} objects into the {@link ApplicationsState.AppEntry#extraInfo} field. 71 * @return {@link ApplicationsState.AppFilter} 72 */ 73 @NonNull getAppFilter()74 public ApplicationsState.AppFilter getAppFilter() { 75 return new ApplicationsState.AppFilter() { 76 @Override 77 public void init() { 78 } 79 80 @Override 81 public boolean filterApp(ApplicationsState.AppEntry entry) { 82 entry.extraInfo = createPermissionStateFor(entry.info.packageName, entry.info.uid); 83 return !shouldIgnorePackage( 84 getContext(), entry.info.packageName, customizedIgnoredPackagesArray()) 85 && ((PermissionState) entry.extraInfo).isPermissible(); 86 } 87 }; 88 } 89 90 /** Provide array resource id for customized ignored packages */ 91 public int customizedIgnoredPackagesArray() { 92 return 0; 93 } 94 95 /** 96 * Subclasses may override this to provide an alternate comparator for sorting apps 97 * @return {@link Comparator} for {@link ApplicationsState.AppEntry} objects. 98 */ 99 @Nullable 100 public Comparator<ApplicationsState.AppEntry> getAppComparator() { 101 return ApplicationsState.ALPHA_COMPARATOR; 102 } 103 104 /** 105 * Call to trigger the app list to update 106 */ 107 public void updateAppList() { 108 mManageApplicationsController.updateAppList(); 109 } 110 111 /** 112 * @return AppOps code 113 */ 114 public abstract int getAppOpsOpCode(); 115 116 /** 117 * @return Manifest permission string 118 */ 119 public abstract String getPermission(); 120 121 private boolean hasRequestedAppOpPermission(String permission, String packageName) { 122 try { 123 String[] packages = mIPackageManager.getAppOpPermissionPackages(permission); 124 return ArrayUtils.contains(packages, packageName); 125 } catch (RemoteException exc) { 126 Log.e(TAG, "PackageManager dead. Cannot get permission info"); 127 return false; 128 } 129 } 130 131 private boolean hasPermission(int uid) { 132 try { 133 int result = mIPackageManager.checkUidPermission(getPermission(), uid); 134 return result == PackageManager.PERMISSION_GRANTED; 135 } catch (RemoteException e) { 136 Log.e(TAG, "PackageManager dead. Cannot get permission info"); 137 return false; 138 } 139 } 140 141 private int getAppOpMode(int uid, String packageName) { 142 return mAppOpsManager.checkOpNoThrow(getAppOpsOpCode(), uid, packageName); 143 } 144 145 private PermissionState createPermissionStateFor(String packageName, int uid) { 146 return new PermissionState( 147 hasRequestedAppOpPermission(getPermission(), packageName), 148 hasPermission(uid), 149 getAppOpMode(uid, packageName)); 150 } 151 152 /** 153 * Checks for packages that should be ignored for further processing 154 */ 155 static boolean shouldIgnorePackage(Context context, String packageName, 156 int customizedIgnoredPackagesArray) { 157 if (context == null) { 158 return true; 159 } 160 Set<String> ignoredPackageNames = null; 161 if (customizedIgnoredPackagesArray != 0) { 162 ignoredPackageNames = Arrays.stream(context.getResources() 163 .getStringArray(customizedIgnoredPackagesArray)).collect(Collectors.toSet()); 164 165 } 166 return packageName.equals("android") 167 || packageName.equals("com.android.systemui") 168 || packageName.equals(context.getPackageName()) 169 || (ignoredPackageNames != null && ignoredPackageNames.contains(packageName)); 170 } 171 172 /** 173 * Collection of information to be used as {@link ApplicationsState.AppEntry#extraInfo} objects 174 */ 175 public static class PermissionState { 176 public final boolean permissionRequested; 177 public final boolean permissionGranted; 178 public final int appOpMode; 179 180 private PermissionState(boolean permissionRequested, boolean permissionGranted, 181 int appOpMode) { 182 this.permissionRequested = permissionRequested; 183 this.permissionGranted = permissionGranted; 184 this.appOpMode = appOpMode; 185 } 186 187 /** 188 * @return True if the permission is granted 189 */ 190 public boolean isAllowed() { 191 if (appOpMode == AppOpsManager.MODE_DEFAULT) { 192 return permissionGranted; 193 } else { 194 return appOpMode == AppOpsManager.MODE_ALLOWED; 195 } 196 } 197 198 /** 199 * @return True if the permission is relevant 200 */ 201 public boolean isPermissible() { 202 return appOpMode != AppOpsManager.MODE_DEFAULT || permissionRequested; 203 } 204 205 @Override 206 public String toString() { 207 return "[permissionGranted: " + permissionGranted 208 + ", permissionRequested: " + permissionRequested 209 + ", appOpMode: " + appOpMode 210 + "]"; 211 } 212 } 213 214 @Override 215 @NonNull 216 public Preference getEmptyPreference() { 217 final Preference empty = new Preference(getPreferenceManager().getContext()); 218 empty.setKey("empty"); 219 empty.setTitle(R.string.noApplications); 220 empty.setEnabled(false); 221 return empty; 222 } 223 } 224