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