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