• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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.settings.accessibility;
18 
19 import android.accessibilityservice.AccessibilityServiceInfo;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.admin.DevicePolicyManager;
24 import android.content.ComponentName;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.content.pm.ResolveInfo;
28 import android.net.Uri;
29 import android.os.Binder;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.IBinder;
33 import android.os.UserHandle;
34 import android.os.storage.StorageManager;
35 import android.provider.Settings;
36 import android.text.TextUtils;
37 import android.view.Menu;
38 import android.view.MenuInflater;
39 import android.view.MenuItem;
40 import android.view.accessibility.AccessibilityManager;
41 
42 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
43 import com.android.internal.widget.LockPatternUtils;
44 import com.android.settings.ConfirmDeviceCredentialActivity;
45 import com.android.settings.R;
46 import com.android.settings.widget.ToggleSwitch;
47 import com.android.settings.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
48 import com.android.settingslib.accessibility.AccessibilityUtils;
49 
50 import java.util.List;
51 
52 public class ToggleAccessibilityServicePreferenceFragment
53         extends ToggleFeaturePreferenceFragment implements DialogInterface.OnClickListener {
54 
55     private static final int DIALOG_ID_ENABLE_WARNING = 1;
56     private static final int DIALOG_ID_DISABLE_WARNING = 2;
57 
58     public static final int ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION = 1;
59 
60     private LockPatternUtils mLockPatternUtils;
61 
62     private final SettingsContentObserver mSettingsContentObserver =
63             new SettingsContentObserver(new Handler()) {
64             @Override
65                 public void onChange(boolean selfChange, Uri uri) {
66                     updateSwitchBarToggleSwitch();
67                 }
68             };
69 
70     private ComponentName mComponentName;
71 
72     private int mShownDialogId;
73 
74     @Override
getMetricsCategory()75     public int getMetricsCategory() {
76         return MetricsEvent.ACCESSIBILITY_SERVICE;
77     }
78 
79     @Override
onCreateOptionsMenu(Menu menu, MenuInflater infalter)80     public void onCreateOptionsMenu(Menu menu, MenuInflater infalter) {
81         // Do not call super. We don't want to see the "Help & feedback" option on this page so as
82         // not to confuse users who think they might be able to send feedback about a specific
83         // accessibility service from this page.
84 
85         // We still want to show the "Settings" menu.
86         if (mSettingsTitle != null && mSettingsIntent != null) {
87             MenuItem menuItem = menu.add(mSettingsTitle);
88             menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
89             menuItem.setIntent(mSettingsIntent);
90         }
91 
92     }
93 
94     @Override
onCreate(Bundle savedInstanceState)95     public void onCreate(Bundle savedInstanceState) {
96         super.onCreate(savedInstanceState);
97         mLockPatternUtils = new LockPatternUtils(getActivity());
98     }
99 
100     @Override
onResume()101     public void onResume() {
102         mSettingsContentObserver.register(getContentResolver());
103         updateSwitchBarToggleSwitch();
104         super.onResume();
105     }
106 
107     @Override
onPause()108     public void onPause() {
109         mSettingsContentObserver.unregister(getContentResolver());
110         super.onPause();
111     }
112 
113     @Override
onPreferenceToggled(String preferenceKey, boolean enabled)114     public void onPreferenceToggled(String preferenceKey, boolean enabled) {
115         ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
116         AccessibilityUtils.setAccessibilityServiceState(getActivity(), toggledService, enabled);
117     }
118 
119     // IMPORTANT: Refresh the info since there are dynamically changing
120     // capabilities. For
121     // example, before JellyBean MR2 the user was granting the explore by touch
122     // one.
getAccessibilityServiceInfo()123     private AccessibilityServiceInfo getAccessibilityServiceInfo() {
124         List<AccessibilityServiceInfo> serviceInfos = AccessibilityManager.getInstance(
125                 getActivity()).getInstalledAccessibilityServiceList();
126         final int serviceInfoCount = serviceInfos.size();
127         for (int i = 0; i < serviceInfoCount; i++) {
128             AccessibilityServiceInfo serviceInfo = serviceInfos.get(i);
129             ResolveInfo resolveInfo = serviceInfo.getResolveInfo();
130             if (mComponentName.getPackageName().equals(resolveInfo.serviceInfo.packageName)
131                     && mComponentName.getClassName().equals(resolveInfo.serviceInfo.name)) {
132                 return serviceInfo;
133             }
134         }
135         return null;
136     }
137 
138     @Override
onCreateDialog(int dialogId)139     public Dialog onCreateDialog(int dialogId) {
140         switch (dialogId) {
141             case DIALOG_ID_ENABLE_WARNING: {
142                 mShownDialogId = DIALOG_ID_ENABLE_WARNING;
143                 final AccessibilityServiceInfo info = getAccessibilityServiceInfo();
144                 if (info == null) {
145                     return null;
146                 }
147 
148                 return AccessibilityServiceWarning
149                         .createCapabilitiesDialog(getActivity(), info, this);
150             }
151             case DIALOG_ID_DISABLE_WARNING: {
152                 mShownDialogId = DIALOG_ID_DISABLE_WARNING;
153                 AccessibilityServiceInfo info = getAccessibilityServiceInfo();
154                 if (info == null) {
155                     return null;
156                 }
157                 return new AlertDialog.Builder(getActivity())
158                         .setTitle(getString(R.string.disable_service_title,
159                                 info.getResolveInfo().loadLabel(getPackageManager())))
160                         .setMessage(getString(R.string.disable_service_message,
161                                 info.getResolveInfo().loadLabel(getPackageManager())))
162                         .setCancelable(true)
163                         .setPositiveButton(android.R.string.ok, this)
164                         .setNegativeButton(android.R.string.cancel, this)
165                         .create();
166             }
167             default: {
168                 throw new IllegalArgumentException();
169             }
170         }
171     }
172 
173     @Override
getDialogMetricsCategory(int dialogId)174     public int getDialogMetricsCategory(int dialogId) {
175         if (dialogId == DIALOG_ID_ENABLE_WARNING) {
176             return MetricsEvent.DIALOG_ACCESSIBILITY_SERVICE_ENABLE;
177         } else {
178             return MetricsEvent.DIALOG_ACCESSIBILITY_SERVICE_DISABLE;
179         }
180     }
181 
updateSwitchBarToggleSwitch()182     private void updateSwitchBarToggleSwitch() {
183         final boolean checked = AccessibilityUtils.getEnabledServicesFromSettings(getActivity())
184                 .contains(mComponentName);
185         mSwitchBar.setCheckedInternal(checked);
186     }
187 
188     /**
189      * Return whether the device is encrypted with legacy full disk encryption. Newer devices
190      * should be using File Based Encryption.
191      *
192      * @return true if device is encrypted
193      */
isFullDiskEncrypted()194     private boolean isFullDiskEncrypted() {
195         return StorageManager.isNonDefaultBlockEncrypted();
196     }
197 
198     @Override
onActivityResult(int requestCode, int resultCode, Intent data)199     public void onActivityResult(int requestCode, int resultCode, Intent data) {
200         if (requestCode == ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION) {
201             if (resultCode == Activity.RESULT_OK) {
202                 handleConfirmServiceEnabled(true);
203                 // The user confirmed that they accept weaker encryption when
204                 // enabling the accessibility service, so change encryption.
205                 // Since we came here asynchronously, check encryption again.
206                 if (isFullDiskEncrypted()) {
207                     mLockPatternUtils.clearEncryptionPassword();
208                     Settings.Global.putInt(getContentResolver(),
209                             Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, 0);
210                 }
211             } else {
212                 handleConfirmServiceEnabled(false);
213             }
214         }
215     }
216 
217     @Override
onClick(DialogInterface dialog, int which)218     public void onClick(DialogInterface dialog, int which) {
219         final boolean checked;
220         switch (which) {
221             case DialogInterface.BUTTON_POSITIVE:
222                 if (mShownDialogId == DIALOG_ID_ENABLE_WARNING) {
223                     if (isFullDiskEncrypted()) {
224                         String title = createConfirmCredentialReasonMessage();
225                         Intent intent = ConfirmDeviceCredentialActivity.createIntent(title, null);
226                         startActivityForResult(intent,
227                                 ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION);
228                     } else {
229                         handleConfirmServiceEnabled(true);
230                     }
231                 } else {
232                     handleConfirmServiceEnabled(false);
233                 }
234                 break;
235             case DialogInterface.BUTTON_NEGATIVE:
236                 checked = (mShownDialogId == DIALOG_ID_DISABLE_WARNING);
237                 handleConfirmServiceEnabled(checked);
238                 break;
239             default:
240                 throw new IllegalArgumentException();
241         }
242     }
243 
handleConfirmServiceEnabled(boolean confirmed)244     private void handleConfirmServiceEnabled(boolean confirmed) {
245         mSwitchBar.setCheckedInternal(confirmed);
246         getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, confirmed);
247         onPreferenceToggled(mPreferenceKey, confirmed);
248     }
249 
createConfirmCredentialReasonMessage()250     private String createConfirmCredentialReasonMessage() {
251         int resId = R.string.enable_service_password_reason;
252         switch (mLockPatternUtils.getKeyguardStoredPasswordQuality(UserHandle.myUserId())) {
253             case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: {
254                 resId = R.string.enable_service_pattern_reason;
255             } break;
256             case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
257             case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: {
258                 resId = R.string.enable_service_pin_reason;
259             } break;
260         }
261         return getString(resId, getAccessibilityServiceInfo().getResolveInfo()
262                 .loadLabel(getPackageManager()));
263     }
264 
265     @Override
onInstallSwitchBarToggleSwitch()266     protected void onInstallSwitchBarToggleSwitch() {
267         super.onInstallSwitchBarToggleSwitch();
268         mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
269                 @Override
270             public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
271                 if (checked) {
272                     mSwitchBar.setCheckedInternal(false);
273                     getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, false);
274                     showDialog(DIALOG_ID_ENABLE_WARNING);
275                 } else {
276                     mSwitchBar.setCheckedInternal(true);
277                     getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, true);
278                     showDialog(DIALOG_ID_DISABLE_WARNING);
279                 }
280                 return true;
281             }
282         });
283     }
284 
285     @Override
onProcessArguments(Bundle arguments)286     protected void onProcessArguments(Bundle arguments) {
287         super.onProcessArguments(arguments);
288         // Settings title and intent.
289         String settingsTitle = arguments.getString(AccessibilitySettings.EXTRA_SETTINGS_TITLE);
290         String settingsComponentName = arguments.getString(
291                 AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME);
292         if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
293             Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
294                     ComponentName.unflattenFromString(settingsComponentName.toString()));
295             if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) {
296                 mSettingsTitle = settingsTitle;
297                 mSettingsIntent = settingsIntent;
298                 setHasOptionsMenu(true);
299             }
300         }
301 
302         mComponentName = arguments.getParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME);
303     }
304 }
305