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 */
16package com.android.settings.applications;
17
18import android.Manifest;
19import android.app.AppGlobals;
20import android.app.AppOpsManager;
21import android.content.Context;
22import android.content.pm.IPackageManager;
23import android.content.pm.PackageManager;
24import android.os.RemoteException;
25import android.util.Log;
26
27import com.android.internal.util.ArrayUtils;
28import com.android.settings.R;
29import com.android.settingslib.applications.ApplicationsState;
30import com.android.settingslib.applications.ApplicationsState.AppEntry;
31import com.android.settingslib.applications.ApplicationsState.AppFilter;
32
33import java.util.List;
34
35/**
36 * Connects app op info to the ApplicationsState. Wraps around the generic AppStateBaseBridge
37 * class to tailor to the semantics of {@link AppOpsManager#OP_REQUEST_INSTALL_PACKAGES}
38 * Also provides app filters that can use the info.
39 */
40public class AppStateInstallAppsBridge extends AppStateBaseBridge {
41
42    private static final String TAG = AppStateInstallAppsBridge.class.getSimpleName();
43
44    private final IPackageManager mIpm;
45    private final AppOpsManager mAppOpsManager;
46
47    public AppStateInstallAppsBridge(Context context, ApplicationsState appState,
48            Callback callback) {
49        super(appState, callback);
50        mIpm = AppGlobals.getPackageManager();
51        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
52    }
53
54    @Override
55    protected void updateExtraInfo(AppEntry app, String packageName, int uid) {
56        app.extraInfo = createInstallAppsStateFor(packageName, uid);
57    }
58
59    @Override
60    protected void loadAllExtraInfo() {
61        // TODO: consider making this a batch operation with a single binder call
62        final List<AppEntry> allApps = mAppSession.getAllApps();
63        for (int i = 0; i < allApps.size(); i++) {
64            AppEntry currentEntry = allApps.get(i);
65            updateExtraInfo(currentEntry, currentEntry.info.packageName, currentEntry.info.uid);
66        }
67    }
68
69    private boolean hasRequestedAppOpPermission(String permission, String packageName) {
70        try {
71            String[] packages = mIpm.getAppOpPermissionPackages(permission);
72            return ArrayUtils.contains(packages, packageName);
73        } catch (RemoteException exc) {
74            Log.e(TAG, "PackageManager dead. Cannot get permission info");
75            return false;
76        }
77    }
78
79    private boolean hasPermission(String permission, int uid) {
80        try {
81            int result = mIpm.checkUidPermission(permission, uid);
82            return result == PackageManager.PERMISSION_GRANTED;
83        } catch (RemoteException e) {
84            Log.e(TAG, "PackageManager dead. Cannot get permission info");
85            return false;
86        }
87    }
88
89    private int getAppOpMode(int appOpCode, int uid, String packageName) {
90        return mAppOpsManager.checkOpNoThrow(appOpCode, uid, packageName);
91    }
92
93    public InstallAppsState createInstallAppsStateFor(String packageName, int uid) {
94        final InstallAppsState appState = new InstallAppsState();
95        appState.permissionRequested = hasRequestedAppOpPermission(
96                Manifest.permission.REQUEST_INSTALL_PACKAGES, packageName);
97        appState.permissionGranted = hasPermission(Manifest.permission.REQUEST_INSTALL_PACKAGES,
98                uid);
99        appState.appOpMode = getAppOpMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, uid,
100                packageName);
101        return appState;
102    }
103
104    /**
105     * Collection of information to be used as {@link AppEntry#extraInfo} objects
106     */
107    public static class InstallAppsState {
108        boolean permissionRequested;
109        boolean permissionGranted;
110        int appOpMode;
111
112        public InstallAppsState() {
113            this.appOpMode = AppOpsManager.MODE_DEFAULT;
114        }
115
116        public boolean canInstallApps() {
117            if (appOpMode == AppOpsManager.MODE_DEFAULT) {
118                return permissionGranted;
119            } else {
120                return appOpMode == AppOpsManager.MODE_ALLOWED;
121            }
122        }
123
124        public boolean isPotentialAppSource() {
125            return appOpMode != AppOpsManager.MODE_DEFAULT || permissionRequested;
126        }
127
128        @Override
129        public String toString() {
130            StringBuilder sb = new StringBuilder("[permissionGranted: " + permissionGranted);
131            sb.append(", permissionRequested: " + permissionRequested);
132            sb.append(", appOpMode: " + appOpMode);
133            sb.append("]");
134            return sb.toString();
135        }
136    }
137
138    public static final AppFilter FILTER_APP_SOURCES = new AppFilter() {
139
140        @Override
141        public void init() {
142        }
143
144        @Override
145        public boolean filterApp(AppEntry info) {
146            if (info.extraInfo == null || !(info.extraInfo instanceof InstallAppsState)) {
147                return false;
148            }
149            InstallAppsState state = (InstallAppsState) info.extraInfo;
150            return state.isPotentialAppSource();
151        }
152    };
153}
154