• 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.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.pm.ResolveInfo;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.UserHandle;
33 import android.os.storage.StorageManager;
34 import android.provider.Settings;
35 import android.text.TextUtils;
36 import android.view.LayoutInflater;
37 import android.view.Menu;
38 import android.view.MenuInflater;
39 import android.view.MenuItem;
40 import android.view.MotionEvent;
41 import android.view.View;
42 import android.view.accessibility.AccessibilityManager;
43 import android.widget.ImageView;
44 import android.widget.LinearLayout;
45 import android.widget.TextView;
46 import android.widget.Toast;
47 
48 import com.android.internal.logging.MetricsProto.MetricsEvent;
49 import com.android.internal.widget.LockPatternUtils;
50 import com.android.settings.ConfirmDeviceCredentialActivity;
51 import com.android.settings.R;
52 import com.android.settings.widget.ToggleSwitch;
53 import com.android.settings.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
54 import com.android.settingslib.accessibility.AccessibilityUtils;
55 
56 import java.util.List;
57 
58 public class ToggleAccessibilityServicePreferenceFragment
59         extends ToggleFeaturePreferenceFragment implements DialogInterface.OnClickListener {
60 
61     private static final int DIALOG_ID_ENABLE_WARNING = 1;
62     private static final int DIALOG_ID_DISABLE_WARNING = 2;
63 
64     public static final int ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION = 1;
65 
66     private LockPatternUtils mLockPatternUtils;
67 
68     private final SettingsContentObserver mSettingsContentObserver =
69             new SettingsContentObserver(new Handler()) {
70             @Override
71                 public void onChange(boolean selfChange, Uri uri) {
72                     updateSwitchBarToggleSwitch();
73                 }
74             };
75 
76     private ComponentName mComponentName;
77 
78     private int mShownDialogId;
79 
80     @Override
getMetricsCategory()81     protected int getMetricsCategory() {
82         return MetricsEvent.ACCESSIBILITY_SERVICE;
83     }
84 
85     @Override
onCreateOptionsMenu(Menu menu, MenuInflater infalter)86     public void onCreateOptionsMenu(Menu menu, MenuInflater infalter) {
87         // Do not call super. We don't want to see the "Help & feedback" option on this page so as
88         // not to confuse users who think they might be able to send feedback about a specific
89         // accessibility service from this page.
90 
91         // We still want to show the "Settings" menu.
92         if (mSettingsTitle != null && mSettingsIntent != null) {
93             MenuItem menuItem = menu.add(mSettingsTitle);
94             menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
95             menuItem.setIntent(mSettingsIntent);
96         }
97 
98     }
99 
100     @Override
onCreate(Bundle savedInstanceState)101     public void onCreate(Bundle savedInstanceState) {
102         super.onCreate(savedInstanceState);
103         mLockPatternUtils = new LockPatternUtils(getActivity());
104     }
105 
106     @Override
onResume()107     public void onResume() {
108         mSettingsContentObserver.register(getContentResolver());
109         updateSwitchBarToggleSwitch();
110         super.onResume();
111     }
112 
113     @Override
onPause()114     public void onPause() {
115         mSettingsContentObserver.unregister(getContentResolver());
116         super.onPause();
117     }
118 
119     @Override
onPreferenceToggled(String preferenceKey, boolean enabled)120     public void onPreferenceToggled(String preferenceKey, boolean enabled) {
121         ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
122         AccessibilityUtils.setAccessibilityServiceState(getActivity(), toggledService, enabled);
123     }
124 
125     // IMPORTANT: Refresh the info since there are dynamically changing
126     // capabilities. For
127     // example, before JellyBean MR2 the user was granting the explore by touch
128     // one.
getAccessibilityServiceInfo()129     private AccessibilityServiceInfo getAccessibilityServiceInfo() {
130         List<AccessibilityServiceInfo> serviceInfos = AccessibilityManager.getInstance(
131                 getActivity()).getInstalledAccessibilityServiceList();
132         final int serviceInfoCount = serviceInfos.size();
133         for (int i = 0; i < serviceInfoCount; i++) {
134             AccessibilityServiceInfo serviceInfo = serviceInfos.get(i);
135             ResolveInfo resolveInfo = serviceInfo.getResolveInfo();
136             if (mComponentName.getPackageName().equals(resolveInfo.serviceInfo.packageName)
137                     && mComponentName.getClassName().equals(resolveInfo.serviceInfo.name)) {
138                 return serviceInfo;
139             }
140         }
141         return null;
142     }
143 
144     @Override
onCreateDialog(int dialogId)145     public Dialog onCreateDialog(int dialogId) {
146         switch (dialogId) {
147             case DIALOG_ID_ENABLE_WARNING: {
148                 mShownDialogId = DIALOG_ID_ENABLE_WARNING;
149 
150                 final AccessibilityServiceInfo info = getAccessibilityServiceInfo();
151                 if (info == null) {
152                     return null;
153                 }
154 
155                 final AlertDialog ad = new AlertDialog.Builder(getActivity())
156                         .setTitle(getString(R.string.enable_service_title,
157                                 info.getResolveInfo().loadLabel(getPackageManager())))
158                         .setView(createEnableDialogContentView(info))
159                         .setCancelable(true)
160                         .setPositiveButton(android.R.string.ok, this)
161                         .setNegativeButton(android.R.string.cancel, this)
162                         .create();
163 
164                 final View.OnTouchListener filterTouchListener = new View.OnTouchListener() {
165                     @Override
166                     public boolean onTouch(View v, MotionEvent event) {
167                         // Filter obscured touches by consuming them.
168                         if ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
169                             if (event.getAction() == MotionEvent.ACTION_UP) {
170                                 Toast.makeText(v.getContext(), R.string.touch_filtered_warning,
171                                         Toast.LENGTH_SHORT).show();
172                             }
173                             return true;
174                         }
175                         return false;
176                     }
177                 };
178 
179                 ad.create();
180                 ad.getButton(AlertDialog.BUTTON_POSITIVE).setOnTouchListener(filterTouchListener);
181                 return ad;
182             }
183             case DIALOG_ID_DISABLE_WARNING: {
184                 mShownDialogId = DIALOG_ID_DISABLE_WARNING;
185                 AccessibilityServiceInfo info = getAccessibilityServiceInfo();
186                 if (info == null) {
187                     return null;
188                 }
189                 return new AlertDialog.Builder(getActivity())
190                         .setTitle(getString(R.string.disable_service_title,
191                                 info.getResolveInfo().loadLabel(getPackageManager())))
192                         .setMessage(getString(R.string.disable_service_message,
193                                 info.getResolveInfo().loadLabel(getPackageManager())))
194                         .setCancelable(true)
195                         .setPositiveButton(android.R.string.ok, this)
196                         .setNegativeButton(android.R.string.cancel, this)
197                         .create();
198             }
199             default: {
200                 throw new IllegalArgumentException();
201             }
202         }
203     }
204 
updateSwitchBarToggleSwitch()205     private void updateSwitchBarToggleSwitch() {
206         final String settingValue = Settings.Secure.getString(getContentResolver(),
207                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
208         final boolean checked = settingValue != null
209                 && settingValue.contains(mComponentName.flattenToString());
210         mSwitchBar.setCheckedInternal(checked);
211     }
212 
213     /**
214      * Return whether the device is encrypted with legacy full disk encryption. Newer devices
215      * should be using File Based Encryption.
216      *
217      * @return true if device is encrypted
218      */
isFullDiskEncrypted()219     private boolean isFullDiskEncrypted() {
220         return StorageManager.isNonDefaultBlockEncrypted();
221     }
222 
createEnableDialogContentView(AccessibilityServiceInfo info)223     private View createEnableDialogContentView(AccessibilityServiceInfo info) {
224         LayoutInflater inflater = (LayoutInflater) getSystemService(
225                 Context.LAYOUT_INFLATER_SERVICE);
226 
227         View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content,
228                 null);
229 
230         TextView encryptionWarningView = (TextView) content.findViewById(
231                 R.id.encryption_warning);
232         if (isFullDiskEncrypted()) {
233             String text = getString(R.string.enable_service_encryption_warning,
234                     info.getResolveInfo().loadLabel(getPackageManager()));
235             encryptionWarningView.setText(text);
236             encryptionWarningView.setVisibility(View.VISIBLE);
237         } else {
238             encryptionWarningView.setVisibility(View.GONE);
239         }
240 
241         TextView capabilitiesHeaderView = (TextView) content.findViewById(
242                 R.id.capabilities_header);
243         capabilitiesHeaderView.setText(getString(R.string.capabilities_list_title,
244                 info.getResolveInfo().loadLabel(getPackageManager())));
245 
246         LinearLayout capabilitiesView = (LinearLayout) content.findViewById(R.id.capabilities);
247 
248         // This capability is implicit for all services.
249         View capabilityView = inflater.inflate(
250                 com.android.internal.R.layout.app_permission_item_old, null);
251 
252         ImageView imageView = (ImageView) capabilityView.findViewById(
253                 com.android.internal.R.id.perm_icon);
254         imageView.setImageDrawable(getActivity().getDrawable(
255                 com.android.internal.R.drawable.ic_text_dot));
256 
257         TextView labelView = (TextView) capabilityView.findViewById(
258                 com.android.internal.R.id.permission_group);
259         labelView.setText(getString(R.string.capability_title_receiveAccessibilityEvents));
260 
261         TextView descriptionView = (TextView) capabilityView.findViewById(
262                 com.android.internal.R.id.permission_list);
263         descriptionView.setText(getString(R.string.capability_desc_receiveAccessibilityEvents));
264 
265         List<AccessibilityServiceInfo.CapabilityInfo> capabilities =
266                 info.getCapabilityInfos();
267 
268         capabilitiesView.addView(capabilityView);
269 
270         // Service specific capabilities.
271         final int capabilityCount = capabilities.size();
272         for (int i = 0; i < capabilityCount; i++) {
273             AccessibilityServiceInfo.CapabilityInfo capability = capabilities.get(i);
274 
275             capabilityView = inflater.inflate(
276                     com.android.internal.R.layout.app_permission_item_old, null);
277 
278             imageView = (ImageView) capabilityView.findViewById(
279                     com.android.internal.R.id.perm_icon);
280             imageView.setImageDrawable(getActivity().getDrawable(
281                     com.android.internal.R.drawable.ic_text_dot));
282 
283             labelView = (TextView) capabilityView.findViewById(
284                     com.android.internal.R.id.permission_group);
285             labelView.setText(getString(capability.titleResId));
286 
287             descriptionView = (TextView) capabilityView.findViewById(
288                     com.android.internal.R.id.permission_list);
289             descriptionView.setText(getString(capability.descResId));
290 
291             capabilitiesView.addView(capabilityView);
292         }
293 
294         return content;
295     }
296 
297     @Override
onActivityResult(int requestCode, int resultCode, Intent data)298     public void onActivityResult(int requestCode, int resultCode, Intent data) {
299         if (requestCode == ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION) {
300             if (resultCode == Activity.RESULT_OK) {
301                 handleConfirmServiceEnabled(true);
302                 // The user confirmed that they accept weaker encryption when
303                 // enabling the accessibility service, so change encryption.
304                 // Since we came here asynchronously, check encryption again.
305                 if (isFullDiskEncrypted()) {
306                     mLockPatternUtils.clearEncryptionPassword();
307                     Settings.Global.putInt(getContentResolver(),
308                             Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, 0);
309                 }
310             } else {
311                 handleConfirmServiceEnabled(false);
312             }
313         }
314     }
315 
316     @Override
onClick(DialogInterface dialog, int which)317     public void onClick(DialogInterface dialog, int which) {
318         final boolean checked;
319         switch (which) {
320             case DialogInterface.BUTTON_POSITIVE:
321                 if (mShownDialogId == DIALOG_ID_ENABLE_WARNING) {
322                     if (isFullDiskEncrypted()) {
323                         String title = createConfirmCredentialReasonMessage();
324                         Intent intent = ConfirmDeviceCredentialActivity.createIntent(title, null);
325                         startActivityForResult(intent,
326                                 ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION);
327                     } else {
328                         handleConfirmServiceEnabled(true);
329                     }
330                 } else {
331                     handleConfirmServiceEnabled(false);
332                 }
333                 break;
334             case DialogInterface.BUTTON_NEGATIVE:
335                 checked = (mShownDialogId == DIALOG_ID_DISABLE_WARNING);
336                 handleConfirmServiceEnabled(checked);
337                 break;
338             default:
339                 throw new IllegalArgumentException();
340         }
341     }
342 
handleConfirmServiceEnabled(boolean confirmed)343     private void handleConfirmServiceEnabled(boolean confirmed) {
344         mSwitchBar.setCheckedInternal(confirmed);
345         getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, confirmed);
346         onPreferenceToggled(mPreferenceKey, confirmed);
347     }
348 
createConfirmCredentialReasonMessage()349     private String createConfirmCredentialReasonMessage() {
350         int resId = R.string.enable_service_password_reason;
351         switch (mLockPatternUtils.getKeyguardStoredPasswordQuality(UserHandle.myUserId())) {
352             case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: {
353                 resId = R.string.enable_service_pattern_reason;
354             } break;
355             case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
356             case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: {
357                 resId = R.string.enable_service_pin_reason;
358             } break;
359         }
360         return getString(resId, getAccessibilityServiceInfo().getResolveInfo()
361                 .loadLabel(getPackageManager()));
362     }
363 
364     @Override
onInstallSwitchBarToggleSwitch()365     protected void onInstallSwitchBarToggleSwitch() {
366         super.onInstallSwitchBarToggleSwitch();
367         mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
368                 @Override
369             public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
370                 if (checked) {
371                     mSwitchBar.setCheckedInternal(false);
372                     getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, false);
373                     showDialog(DIALOG_ID_ENABLE_WARNING);
374                 } else {
375                     mSwitchBar.setCheckedInternal(true);
376                     getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, true);
377                     showDialog(DIALOG_ID_DISABLE_WARNING);
378                 }
379                 return true;
380             }
381         });
382     }
383 
384     @Override
onProcessArguments(Bundle arguments)385     protected void onProcessArguments(Bundle arguments) {
386         super.onProcessArguments(arguments);
387         // Settings title and intent.
388         String settingsTitle = arguments.getString(AccessibilitySettings.EXTRA_SETTINGS_TITLE);
389         String settingsComponentName = arguments.getString(
390                 AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME);
391         if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
392             Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
393                     ComponentName.unflattenFromString(settingsComponentName.toString()));
394             if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) {
395                 mSettingsTitle = settingsTitle;
396                 mSettingsIntent = settingsIntent;
397                 setHasOptionsMenu(true);
398             }
399         }
400 
401         mComponentName = arguments.getParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME);
402     }
403 }
404