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