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