1 /* 2 * Copyright (C) 2017 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.accessibility; 17 18 import android.accessibilityservice.AccessibilityServiceInfo; 19 import android.annotation.Nullable; 20 import android.content.ComponentName; 21 import android.content.ContentResolver; 22 import android.content.Context; 23 import android.database.ContentObserver; 24 import android.os.Bundle; 25 import android.os.Handler; 26 import android.os.UserHandle; 27 import android.provider.Settings; 28 import android.support.v14.preference.SwitchPreference; 29 import android.support.v7.preference.Preference; 30 import android.view.accessibility.AccessibilityManager; 31 import android.widget.Switch; 32 33 import com.android.internal.accessibility.AccessibilityShortcutController; 34 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 35 import com.android.settings.R; 36 import com.android.settings.search.BaseSearchIndexProvider; 37 import com.android.settings.search.Indexable; 38 import com.android.settingslib.accessibility.AccessibilityUtils; 39 40 /** 41 * Settings page for accessibility shortcut 42 */ 43 public class AccessibilityShortcutPreferenceFragment extends ToggleFeaturePreferenceFragment 44 implements Indexable { 45 46 public static final String SHORTCUT_SERVICE_KEY = "accessibility_shortcut_service"; 47 public static final String ON_LOCK_SCREEN_KEY = "accessibility_shortcut_on_lock_screen"; 48 49 private Preference mServicePreference; 50 private SwitchPreference mOnLockScreenSwitchPreference; 51 private final ContentObserver mContentObserver = new ContentObserver(new Handler()) { 52 @Override 53 public void onChange(boolean selfChange) { 54 updatePreferences(); 55 } 56 }; 57 58 @Override getMetricsCategory()59 public int getMetricsCategory() { 60 return MetricsEvent.ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE; 61 } 62 63 @Override getHelpResource()64 public int getHelpResource() { 65 return R.string.help_url_accessibility_shortcut; 66 } 67 68 @Override onCreate(Bundle savedInstanceState)69 public void onCreate(Bundle savedInstanceState) { 70 super.onCreate(savedInstanceState); 71 72 mServicePreference = findPreference(SHORTCUT_SERVICE_KEY); 73 mOnLockScreenSwitchPreference = (SwitchPreference) findPreference(ON_LOCK_SCREEN_KEY); 74 mOnLockScreenSwitchPreference.setOnPreferenceChangeListener((Preference p, Object o) -> { 75 Settings.Secure.putInt(getContentResolver(), 76 Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, 77 ((Boolean) o) ? 1 : 0); 78 return true; 79 }); 80 mFooterPreferenceMixin.createFooterPreference() 81 .setTitle(R.string.accessibility_shortcut_description); 82 } 83 84 @Override onResume()85 public void onResume() { 86 super.onResume(); 87 updatePreferences(); 88 getContentResolver().registerContentObserver( 89 Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN), 90 false, mContentObserver); 91 } 92 93 @Override onPause()94 public void onPause() { 95 getContentResolver().unregisterContentObserver(mContentObserver); 96 super.onPause(); 97 } 98 99 @Override getPreferenceScreenResId()100 protected int getPreferenceScreenResId() { 101 return R.xml.accessibility_shortcut_settings; 102 } 103 104 @Override onInstallSwitchBarToggleSwitch()105 protected void onInstallSwitchBarToggleSwitch() { 106 super.onInstallSwitchBarToggleSwitch(); 107 mSwitchBar.addOnSwitchChangeListener((Switch switchView, boolean enabled) -> { 108 Context context = getContext(); 109 if (enabled && !shortcutFeatureAvailable(context)) { 110 // If no service is configured, we'll disable the shortcut shortly. Give the 111 // user a chance to select a service. We'll update the preferences when we resume. 112 Settings.Secure.putInt( 113 getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1); 114 mServicePreference.setEnabled(true); 115 mServicePreference.performClick(); 116 } else { 117 onPreferenceToggled(Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, enabled); 118 } 119 }); 120 } 121 122 @Override onPreferenceToggled(String preferenceKey, boolean enabled)123 protected void onPreferenceToggled(String preferenceKey, boolean enabled) { 124 Settings.Secure.putInt(getContentResolver(), preferenceKey, enabled ? 1 : 0); 125 updatePreferences(); 126 } 127 updatePreferences()128 private void updatePreferences() { 129 ContentResolver cr = getContentResolver(); 130 Context context = getContext(); 131 mServicePreference.setSummary(getServiceName(context)); 132 if (!shortcutFeatureAvailable(context)) { 133 // If no service is configured, make sure the overall shortcut is turned off 134 Settings.Secure.putInt( 135 getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 0); 136 } 137 boolean isEnabled = Settings.Secure 138 .getInt(cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED, 1) == 1; 139 mSwitchBar.setChecked(isEnabled); 140 // The shortcut is enabled by default on the lock screen as long as the user has 141 // enabled the shortcut with the warning dialog 142 final int dialogShown = Settings.Secure.getInt( 143 cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0); 144 final boolean enabledFromLockScreen = Settings.Secure.getInt( 145 cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, dialogShown) == 1; 146 mOnLockScreenSwitchPreference.setChecked(enabledFromLockScreen); 147 // Only enable changing the service and lock screen behavior if the shortcut is on 148 mServicePreference.setEnabled(mToggleSwitch.isChecked()); 149 mOnLockScreenSwitchPreference.setEnabled(mToggleSwitch.isChecked()); 150 } 151 152 /** 153 * Get the user-visible name of the service currently selected for the shortcut. 154 * 155 * @param context The current context 156 * @return The name of the service or a string saying that none is selected. 157 */ getServiceName(Context context)158 public static CharSequence getServiceName(Context context) { 159 if (!shortcutFeatureAvailable(context)) { 160 return context.getString(R.string.accessibility_no_service_selected); 161 } 162 AccessibilityServiceInfo shortcutServiceInfo = getServiceInfo(context); 163 if (shortcutServiceInfo != null) { 164 return shortcutServiceInfo.getResolveInfo().loadLabel(context.getPackageManager()); 165 } 166 return AccessibilityShortcutController.getFrameworkShortcutFeaturesMap() 167 .get(getShortcutComponent(context)).getLabel(context); 168 } 169 getServiceInfo(Context context)170 private static AccessibilityServiceInfo getServiceInfo(Context context) { 171 return AccessibilityManager.getInstance(context) 172 .getInstalledServiceInfoWithComponentName(getShortcutComponent(context)); 173 } 174 shortcutFeatureAvailable(Context context)175 private static boolean shortcutFeatureAvailable(Context context) { 176 ComponentName shortcutFeature = getShortcutComponent(context); 177 if (shortcutFeature == null) return false; 178 179 if (AccessibilityShortcutController.getFrameworkShortcutFeaturesMap() 180 .containsKey(shortcutFeature)) { 181 return true; 182 } 183 return getServiceInfo(context) != null; 184 } 185 getShortcutComponent(Context context)186 private static @Nullable ComponentName getShortcutComponent(Context context) { 187 String componentNameString = AccessibilityUtils.getShortcutTargetServiceComponentNameString( 188 context, UserHandle.myUserId()); 189 if (componentNameString == null) return null; 190 return ComponentName.unflattenFromString(componentNameString); 191 } 192 193 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 194 new BaseSearchIndexProvider() { 195 // This fragment is for details of the shortcut. Only the shortcut itself needs 196 // to be indexed. 197 protected boolean isPageSearchEnabled(Context context) { 198 return false; 199 } 200 }; 201 } 202