1 /*
2  * Copyright (C) 2015 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 package com.android.settings.applications;
17 
18 import static android.app.AppOpsManager.OP_GET_USAGE_STATS;
19 import static android.app.AppOpsManager.OP_LOADER_USAGE_STATS;
20 
21 import android.Manifest;
22 import android.app.AppOpsManager;
23 import android.app.admin.DevicePolicyManager;
24 import android.app.settings.SettingsEnums;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.PackageManager;
29 import android.content.pm.ResolveInfo;
30 import android.os.Bundle;
31 import android.provider.Settings;
32 
33 import androidx.annotation.VisibleForTesting;
34 import androidx.appcompat.app.AlertDialog;
35 import androidx.preference.Preference;
36 import androidx.preference.Preference.OnPreferenceChangeListener;
37 import androidx.preference.Preference.OnPreferenceClickListener;
38 import androidx.preference.SwitchPreference;
39 
40 import com.android.settings.R;
41 import com.android.settings.applications.AppStateUsageBridge.UsageState;
42 import com.android.settings.overlay.FeatureFactory;
43 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
44 
45 public class UsageAccessDetails extends AppInfoWithHeader implements OnPreferenceChangeListener,
46         OnPreferenceClickListener {
47 
48     private static final String KEY_APP_OPS_PREFERENCE_SCREEN = "app_ops_preference_screen";
49     private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch";
50     private static final String KEY_APP_OPS_SETTINGS_DESC = "app_ops_settings_description";
51 
52     // Use a bridge to get the usage stats but don't initialize it to connect with all state.
53     // TODO: Break out this functionality into its own class.
54     private AppStateUsageBridge mUsageBridge;
55     private AppOpsManager mAppOpsManager;
56     private SwitchPreference mSwitchPref;
57     private Preference mUsageDesc;
58     private Intent mSettingsIntent;
59     private UsageState mUsageState;
60     private DevicePolicyManager mDpm;
61 
62     @Override
onCreate(Bundle savedInstanceState)63     public void onCreate(Bundle savedInstanceState) {
64         super.onCreate(savedInstanceState);
65 
66         Context context = getActivity();
67         mUsageBridge = new AppStateUsageBridge(context, mState, null);
68         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
69         mDpm = context.getSystemService(DevicePolicyManager.class);
70 
71         addPreferencesFromResource(R.xml.app_ops_permissions_details);
72         mSwitchPref = (SwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
73         mUsageDesc = findPreference(KEY_APP_OPS_SETTINGS_DESC);
74 
75         getPreferenceScreen().setTitle(R.string.usage_access);
76         mSwitchPref.setTitle(R.string.permit_usage_access);
77         mUsageDesc.setSummary(R.string.usage_access_description);
78 
79         mSwitchPref.setOnPreferenceChangeListener(this);
80 
81         mSettingsIntent = new Intent(Intent.ACTION_MAIN)
82                 .addCategory(Settings.INTENT_CATEGORY_USAGE_ACCESS_CONFIG)
83                 .setPackage(mPackageName);
84     }
85 
86     @Override
onPreferenceClick(Preference preference)87     public boolean onPreferenceClick(Preference preference) {
88         return false;
89     }
90 
91     @Override
onPreferenceChange(Preference preference, Object newValue)92     public boolean onPreferenceChange(Preference preference, Object newValue) {
93         if (preference == mSwitchPref) {
94             if (mUsageState != null && (Boolean) newValue != mUsageState.isPermissible()) {
95                 if (mUsageState.isPermissible() && mDpm.isProfileOwnerApp(mPackageName)) {
96                     new AlertDialog.Builder(getContext())
97                             .setIcon(com.android.internal.R.drawable.ic_dialog_alert_material)
98                             .setTitle(android.R.string.dialog_alert_title)
99                             .setMessage(R.string.work_profile_usage_access_warning)
100                             .setPositiveButton(R.string.okay, null)
101                             .show();
102                 }
103                 setHasAccess(!mUsageState.isPermissible());
104                 refreshUi();
105             }
106             return true;
107         }
108         return false;
109     }
110 
doesAnyPermissionMatch(String permissionToMatch, String[] permissions)111     private static boolean doesAnyPermissionMatch(String permissionToMatch, String[] permissions) {
112         for (String permission : permissions) {
113             if (permissionToMatch.equals(permission)) {
114                 return true;
115             }
116         }
117         return false;
118     }
119 
setHasAccess(boolean newState)120     private void setHasAccess(boolean newState) {
121         logSpecialPermissionChange(newState, mPackageName);
122 
123         final int newAppOpMode = newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED;
124         final int uid = mPackageInfo.applicationInfo.uid;
125         if (doesAnyPermissionMatch(Manifest.permission.PACKAGE_USAGE_STATS,
126                 mUsageState.packageInfo.requestedPermissions)) {
127             mAppOpsManager.setMode(OP_GET_USAGE_STATS, uid, mPackageName, newAppOpMode);
128         }
129         if (doesAnyPermissionMatch(Manifest.permission.LOADER_USAGE_STATS,
130                 mUsageState.packageInfo.requestedPermissions)) {
131             mAppOpsManager.setMode(OP_LOADER_USAGE_STATS, uid, mPackageName, newAppOpMode);
132         }
133     }
134 
135     @VisibleForTesting
logSpecialPermissionChange(boolean newState, String packageName)136     void logSpecialPermissionChange(boolean newState, String packageName) {
137         int logCategory = newState ? SettingsEnums.APP_SPECIAL_PERMISSION_USAGE_VIEW_ALLOW
138                 : SettingsEnums.APP_SPECIAL_PERMISSION_USAGE_VIEW_DENY;
139         final MetricsFeatureProvider metricsFeatureProvider =
140                 FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider();
141         metricsFeatureProvider.action(
142                 metricsFeatureProvider.getAttribution(getActivity()),
143                 logCategory,
144                 getMetricsCategory(),
145                 packageName,
146                 0);
147     }
148 
149     @Override
refreshUi()150     protected boolean refreshUi() {
151         retrieveAppEntry();
152         if (mAppEntry == null) {
153             return false;
154         }
155         if (mPackageInfo == null) {
156             return false; // onCreate must have failed, make sure to exit
157         }
158         mUsageState = mUsageBridge.getUsageInfo(mPackageName,
159                 mPackageInfo.applicationInfo.uid);
160 
161         boolean hasAccess = mUsageState.isPermissible();
162         mSwitchPref.setChecked(hasAccess);
163         mSwitchPref.setEnabled(mUsageState.permissionDeclared);
164 
165         ResolveInfo resolveInfo = mPm.resolveActivityAsUser(mSettingsIntent,
166                 PackageManager.GET_META_DATA, mUserId);
167         if (resolveInfo != null) {
168             Bundle metaData = resolveInfo.activityInfo.metaData;
169             mSettingsIntent.setComponent(new ComponentName(resolveInfo.activityInfo.packageName,
170                     resolveInfo.activityInfo.name));
171             if (metaData != null
172                     && metaData.containsKey(Settings.METADATA_USAGE_ACCESS_REASON)) {
173                 mSwitchPref.setSummary(
174                         metaData.getString(Settings.METADATA_USAGE_ACCESS_REASON));
175             }
176         }
177 
178         return true;
179     }
180 
181     @Override
createDialog(int id, int errorCode)182     protected AlertDialog createDialog(int id, int errorCode) {
183         return null;
184     }
185 
186     @Override
getMetricsCategory()187     public int getMetricsCategory() {
188         return SettingsEnums.APPLICATIONS_USAGE_ACCESS_DETAIL;
189     }
190 
191 }
192