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.appinfo;
17 
18 import android.app.AppOpsManager;
19 import android.app.settings.SettingsEnums;
20 import android.content.Context;
21 import android.os.Bundle;
22 import android.view.LayoutInflater;
23 import android.view.View;
24 import android.view.ViewGroup;
25 
26 import androidx.annotation.Nullable;
27 import androidx.annotation.VisibleForTesting;
28 import androidx.appcompat.app.AlertDialog;
29 import androidx.preference.Preference;
30 import androidx.preference.Preference.OnPreferenceChangeListener;
31 import androidx.preference.Preference.OnPreferenceClickListener;
32 import androidx.preference.TwoStatePreference;
33 
34 import com.android.settings.R;
35 import com.android.settings.Utils;
36 import com.android.settings.applications.AppInfoWithHeader;
37 import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
38 import com.android.settings.applications.AppStateOverlayBridge;
39 import com.android.settings.applications.AppStateOverlayBridge.OverlayState;
40 import com.android.settings.overlay.FeatureFactory;
41 import com.android.settingslib.applications.ApplicationsState.AppEntry;
42 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
43 
44 public class DrawOverlayDetails extends AppInfoWithHeader implements OnPreferenceChangeListener,
45         OnPreferenceClickListener {
46 
47     private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch";
48     private static final String LOG_TAG = "DrawOverlayDetails";
49 
50     // Use a bridge to get the overlay details but don't initialize it to connect with all state.
51     // TODO: Break out this functionality into its own class.
52     private AppStateOverlayBridge mOverlayBridge;
53     private AppOpsManager mAppOpsManager;
54     @Nullable
55     private TwoStatePreference mSwitchPref = null;
56     private OverlayState mOverlayState;
57 
58     @Override
onCreate(Bundle savedInstanceState)59     public void onCreate(Bundle savedInstanceState) {
60         super.onCreate(savedInstanceState);
61 
62         Context context = getActivity();
63         mOverlayBridge = new AppStateOverlayBridge(context, mState, null);
64         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
65 
66         if (!Utils.isSystemAlertWindowEnabled(context)) {
67             mPackageInfo = null;
68             return;
69         }
70 
71         // find preferences
72         addPreferencesFromResource(R.xml.draw_overlay_permissions_details);
73         mSwitchPref = findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
74 
75         // install event listeners
76         mSwitchPref.setOnPreferenceChangeListener(this);
77     }
78 
79     // Override here so we don't have an empty screen
80     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)81     public View onCreateView(LayoutInflater inflater,
82             ViewGroup container,
83             Bundle savedInstanceState) {
84         // if we don't have a package info, show a page saying this is unsupported
85         if (mPackageInfo == null) {
86             return inflater.inflate(R.layout.manage_applications_apps_unsupported, null);
87         }
88         return super.onCreateView(inflater, container, savedInstanceState);
89     }
90 
91     @Override
onDestroy()92     public void onDestroy() {
93         super.onDestroy();
94         mOverlayBridge.release();
95     }
96 
97     @Override
onPreferenceClick(Preference preference)98     public boolean onPreferenceClick(Preference preference) {
99         return false;
100     }
101 
102     @Override
onPreferenceChange(Preference preference, Object newValue)103     public boolean onPreferenceChange(Preference preference, Object newValue) {
104         if (preference == mSwitchPref) {
105             if (mOverlayState != null && (Boolean) newValue != mOverlayState.isPermissible()) {
106                 setCanDrawOverlay(!mOverlayState.isPermissible());
107                 refreshUi();
108             }
109             return true;
110         }
111         return false;
112     }
113 
setCanDrawOverlay(boolean newState)114     private void setCanDrawOverlay(boolean newState) {
115         logSpecialPermissionChange(newState, mPackageName);
116         mAppOpsManager.setMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
117                 mPackageInfo.applicationInfo.uid, mPackageName, newState
118                         ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
119     }
120 
121     @VisibleForTesting
logSpecialPermissionChange(boolean newState, String packageName)122     void logSpecialPermissionChange(boolean newState, String packageName) {
123         int logCategory = newState ? SettingsEnums.APP_SPECIAL_PERMISSION_APPDRAW_ALLOW
124                 : SettingsEnums.APP_SPECIAL_PERMISSION_APPDRAW_DENY;
125         final MetricsFeatureProvider metricsFeatureProvider =
126                 FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
127         metricsFeatureProvider.action(
128                 metricsFeatureProvider.getAttribution(getActivity()),
129                 logCategory,
130                 getMetricsCategory(),
131                 packageName,
132                 0);
133     }
134 
135     @Override
refreshUi()136     protected boolean refreshUi() {
137         if (mPackageInfo == null) {
138             return true;
139         }
140 
141         mOverlayState = mOverlayBridge.getOverlayInfo(mPackageName,
142                 mPackageInfo.applicationInfo.uid);
143 
144         boolean isAllowed = mOverlayState.isPermissible();
145         mSwitchPref.setChecked(isAllowed);
146         // you cannot ask a user to grant you a permission you did not have!
147         mSwitchPref.setEnabled(mOverlayState.permissionDeclared && mOverlayState.controlEnabled);
148 
149         return true;
150     }
151 
152     @Override
createDialog(int id, int errorCode)153     protected AlertDialog createDialog(int id, int errorCode) {
154         return null;
155     }
156 
157     @Override
getMetricsCategory()158     public int getMetricsCategory() {
159         return SettingsEnums.SYSTEM_ALERT_WINDOW_APPS;
160     }
161 
getSummary(Context context, AppEntry entry)162     public static CharSequence getSummary(Context context, AppEntry entry) {
163         if (entry == null) {
164             return "";
165         }
166 
167         OverlayState state;
168         if (entry.extraInfo instanceof OverlayState) {
169             state = (OverlayState) entry.extraInfo;
170         } else if (entry.extraInfo instanceof PermissionState) {
171             state = new OverlayState((PermissionState) entry.extraInfo);
172         } else {
173             state = new AppStateOverlayBridge(context, null, null).getOverlayInfo(
174                     entry.info.packageName, entry.info.uid);
175         }
176 
177         return getSummary(context, state);
178     }
179 
getSummary(Context context, OverlayState overlayState)180     public static CharSequence getSummary(Context context, OverlayState overlayState) {
181         return context.getString(overlayState.isPermissible() ?
182                 R.string.app_permission_summary_allowed
183                 : R.string.app_permission_summary_not_allowed);
184     }
185 }
186