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