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 static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME; 19 import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME; 20 21 import android.accessibilityservice.AccessibilityServiceInfo; 22 import android.app.Dialog; 23 import android.app.settings.SettingsEnums; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 29 import android.graphics.drawable.Drawable; 30 import android.os.Binder; 31 import android.os.Bundle; 32 import android.os.IBinder; 33 import android.os.UserHandle; 34 import android.provider.Settings; 35 import android.text.TextUtils; 36 import android.view.accessibility.AccessibilityManager; 37 import android.view.View; 38 39 import androidx.fragment.app.Fragment; 40 import androidx.fragment.app.FragmentActivity; 41 42 import com.android.internal.accessibility.AccessibilityShortcutController; 43 import com.android.internal.accessibility.AccessibilityShortcutController.ToggleableFrameworkFeatureInfo; 44 import com.android.settings.R; 45 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 46 import com.android.settings.widget.RadioButtonPickerFragment; 47 import com.android.settings.widget.RadioButtonPreference; 48 import com.android.settingslib.accessibility.AccessibilityUtils; 49 import com.android.settingslib.widget.CandidateInfo; 50 51 import java.util.ArrayList; 52 import java.util.List; 53 import java.util.Map; 54 55 /** 56 * Fragment for picking accessibility shortcut service 57 */ 58 public class ShortcutServicePickerFragment extends RadioButtonPickerFragment { 59 60 @Override getMetricsCategory()61 public int getMetricsCategory() { 62 return SettingsEnums.ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE; 63 } 64 65 @Override getPreferenceScreenResId()66 protected int getPreferenceScreenResId() { 67 return R.xml.accessibility_shortcut_service_settings; 68 } 69 70 @Override getCandidates()71 protected List<? extends CandidateInfo> getCandidates() { 72 final Context context = getContext(); 73 final AccessibilityManager accessibilityManager = context 74 .getSystemService(AccessibilityManager.class); 75 final List<AccessibilityServiceInfo> installedServices = 76 accessibilityManager.getInstalledAccessibilityServiceList(); 77 final int numInstalledServices = installedServices.size(); 78 79 final List<CandidateInfo> candidates = new ArrayList<>(numInstalledServices); 80 Map<ComponentName, ToggleableFrameworkFeatureInfo> frameworkFeatureInfoMap = 81 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap(); 82 for (ComponentName componentName : frameworkFeatureInfoMap.keySet()) { 83 final int iconId; 84 if (componentName.equals(COLOR_INVERSION_COMPONENT_NAME)) { 85 iconId = R.drawable.ic_color_inversion; 86 } else if (componentName.equals(DALTONIZER_COMPONENT_NAME)) { 87 iconId = R.drawable.ic_daltonizer; 88 } else { 89 iconId = R.drawable.empty_icon; 90 } 91 candidates.add(new FrameworkCandidateInfo(frameworkFeatureInfoMap.get(componentName), 92 iconId, componentName.flattenToString())); 93 } 94 for (int i = 0; i < numInstalledServices; i++) { 95 candidates.add(new ServiceCandidateInfo(installedServices.get(i))); 96 } 97 98 return candidates; 99 } 100 101 @Override getDefaultKey()102 protected String getDefaultKey() { 103 String shortcutServiceString = AccessibilityUtils 104 .getShortcutTargetServiceComponentNameString(getContext(), UserHandle.myUserId()); 105 if (shortcutServiceString != null) { 106 ComponentName shortcutName = ComponentName.unflattenFromString(shortcutServiceString); 107 if (shortcutName != null) { 108 return shortcutName.flattenToString(); 109 } 110 } 111 return null; 112 } 113 114 @Override setDefaultKey(String key)115 protected boolean setDefaultKey(String key) { 116 Settings.Secure.putString(getContext().getContentResolver(), 117 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, key); 118 return true; 119 } 120 121 @Override onRadioButtonClicked(RadioButtonPreference selected)122 public void onRadioButtonClicked(RadioButtonPreference selected) { 123 final String selectedKey = selected.getKey(); 124 125 if (TextUtils.isEmpty(selectedKey)) { 126 super.onRadioButtonClicked(selected); 127 } else { 128 final ComponentName selectedComponent = ComponentName.unflattenFromString(selectedKey); 129 if (AccessibilityShortcutController.getFrameworkShortcutFeaturesMap() 130 .containsKey(selectedComponent)) { 131 // This is a framework feature. It doesn't need to be confirmed. 132 onRadioButtonConfirmed(selectedKey); 133 } else { 134 final FragmentActivity activity = getActivity(); 135 if (activity != null) { 136 ConfirmationDialogFragment.newInstance(this, selectedKey) 137 .show(activity.getSupportFragmentManager(), 138 ConfirmationDialogFragment.TAG); 139 } 140 } 141 } 142 } 143 onServiceConfirmed(String serviceKey)144 private void onServiceConfirmed(String serviceKey) { 145 onRadioButtonConfirmed(serviceKey); 146 } 147 148 public static class ConfirmationDialogFragment extends InstrumentedDialogFragment 149 implements View.OnClickListener { 150 private static final String EXTRA_KEY = "extra_key"; 151 private static final String TAG = "ConfirmationDialogFragment"; 152 private IBinder mToken; 153 newInstance(ShortcutServicePickerFragment parent, String key)154 public static ConfirmationDialogFragment newInstance(ShortcutServicePickerFragment parent, 155 String key) { 156 final ConfirmationDialogFragment fragment = new ConfirmationDialogFragment(); 157 final Bundle argument = new Bundle(); 158 argument.putString(EXTRA_KEY, key); 159 fragment.setArguments(argument); 160 fragment.setTargetFragment(parent, 0); 161 fragment.mToken = new Binder(); 162 return fragment; 163 } 164 165 @Override getMetricsCategory()166 public int getMetricsCategory() { 167 return SettingsEnums.ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE; 168 } 169 170 @Override onCreateDialog(Bundle savedInstanceState)171 public Dialog onCreateDialog(Bundle savedInstanceState) { 172 final Bundle bundle = getArguments(); 173 final String key = bundle.getString(EXTRA_KEY); 174 final ComponentName serviceComponentName = ComponentName.unflattenFromString(key); 175 final AccessibilityManager accessibilityManager = getActivity() 176 .getSystemService(AccessibilityManager.class); 177 AccessibilityServiceInfo info = accessibilityManager 178 .getInstalledServiceInfoWithComponentName(serviceComponentName); 179 return AccessibilityServiceWarning.createCapabilitiesDialog(getActivity(), info, this); 180 } 181 182 @Override onClick(View view)183 public void onClick(View view) { 184 final Fragment fragment = getTargetFragment(); 185 if ((view.getId() == R.id.permission_enable_allow_button) 186 && (fragment instanceof ShortcutServicePickerFragment)) { 187 final Bundle bundle = getArguments(); 188 ((ShortcutServicePickerFragment) fragment).onServiceConfirmed( 189 bundle.getString(EXTRA_KEY)); 190 } 191 dismiss(); 192 } 193 } 194 195 private class FrameworkCandidateInfo extends CandidateInfo { 196 final ToggleableFrameworkFeatureInfo mToggleableFrameworkFeatureInfo; 197 final int mIconResId; 198 final String mKey; 199 FrameworkCandidateInfo( ToggleableFrameworkFeatureInfo frameworkFeatureInfo, int iconResId, String key)200 public FrameworkCandidateInfo( 201 ToggleableFrameworkFeatureInfo frameworkFeatureInfo, int iconResId, String key) { 202 super(true /* enabled */); 203 mToggleableFrameworkFeatureInfo = frameworkFeatureInfo; 204 mIconResId = iconResId; 205 mKey = key; 206 } 207 208 @Override loadLabel()209 public CharSequence loadLabel() { 210 return mToggleableFrameworkFeatureInfo.getLabel(getContext()); 211 } 212 213 @Override loadIcon()214 public Drawable loadIcon() { 215 return getContext().getDrawable(mIconResId); 216 } 217 218 @Override getKey()219 public String getKey() { 220 return mKey; 221 } 222 } 223 224 private class ServiceCandidateInfo extends CandidateInfo { 225 final AccessibilityServiceInfo mServiceInfo; 226 ServiceCandidateInfo(AccessibilityServiceInfo serviceInfo)227 public ServiceCandidateInfo(AccessibilityServiceInfo serviceInfo) { 228 super(true /* enabled */); 229 mServiceInfo = serviceInfo; 230 } 231 232 @Override loadLabel()233 public CharSequence loadLabel() { 234 final PackageManager pmw = getContext().getPackageManager(); 235 final CharSequence label = 236 mServiceInfo.getResolveInfo().serviceInfo.loadLabel(pmw); 237 if (label != null) { 238 return label; 239 } 240 241 final ComponentName componentName = mServiceInfo.getComponentName(); 242 if (componentName != null) { 243 try { 244 final ApplicationInfo appInfo = pmw.getApplicationInfoAsUser( 245 componentName.getPackageName(), 0, UserHandle.myUserId()); 246 return appInfo.loadLabel(pmw); 247 } catch (PackageManager.NameNotFoundException e) { 248 return null; 249 } 250 } 251 return null; 252 } 253 254 @Override loadIcon()255 public Drawable loadIcon() { 256 final ResolveInfo resolveInfo = mServiceInfo.getResolveInfo(); 257 return (resolveInfo.getIconResource() == 0) 258 ? getContext().getDrawable(R.drawable.ic_accessibility_generic) 259 : resolveInfo.loadIcon(getContext().getPackageManager()); 260 } 261 262 @Override getKey()263 public String getKey() { 264 return mServiceInfo.getComponentName().flattenToString(); 265 } 266 } 267 } 268