1 /* 2 * Copyright (C) 2018 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.gestures; 18 19 import static android.os.UserHandle.USER_CURRENT; 20 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; 21 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY; 22 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; 23 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY; 24 25 import android.app.settings.SettingsEnums; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.SharedPreferences; 29 import android.content.om.IOverlayManager; 30 import android.content.om.OverlayInfo; 31 import android.os.Bundle; 32 import android.os.RemoteException; 33 import android.os.ServiceManager; 34 import android.provider.Settings; 35 import android.text.TextUtils; 36 import android.view.accessibility.AccessibilityManager; 37 38 import androidx.annotation.Nullable; 39 import androidx.annotation.VisibleForTesting; 40 import androidx.preference.PreferenceScreen; 41 42 import com.android.settings.R; 43 import com.android.settings.accessibility.AccessibilityGestureNavigationTutorial; 44 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; 45 import com.android.settings.overlay.FeatureFactory; 46 import com.android.settings.search.BaseSearchIndexProvider; 47 import com.android.settings.support.actionbar.HelpResourceProvider; 48 import com.android.settings.utils.CandidateInfoExtra; 49 import com.android.settings.widget.RadioButtonPickerFragment; 50 import com.android.settingslib.search.SearchIndexable; 51 import com.android.settingslib.widget.CandidateInfo; 52 import com.android.settingslib.widget.IllustrationPreference; 53 import com.android.settingslib.widget.RadioButtonPreference; 54 55 import java.util.ArrayList; 56 import java.util.List; 57 58 @SearchIndexable 59 public class SystemNavigationGestureSettings extends RadioButtonPickerFragment implements 60 HelpResourceProvider { 61 62 @VisibleForTesting 63 static final String KEY_SYSTEM_NAV_3BUTTONS = "system_nav_3buttons"; 64 @VisibleForTesting 65 static final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons"; 66 @VisibleForTesting 67 static final String KEY_SYSTEM_NAV_GESTURAL = "system_nav_gestural"; 68 69 public static final String PREF_KEY_SUGGESTION_COMPLETE = 70 "pref_system_navigation_suggestion_complete"; 71 72 private static final String KEY_SHOW_A11Y_TUTORIAL_DIALOG = "show_a11y_tutorial_dialog_bool"; 73 74 private boolean mA11yTutorialDialogShown = false; 75 76 private IOverlayManager mOverlayManager; 77 78 private IllustrationPreference mVideoPreference; 79 80 @Override onCreate(@ullable Bundle savedInstanceState)81 public void onCreate(@Nullable Bundle savedInstanceState) { 82 super.onCreate(savedInstanceState); 83 if (savedInstanceState != null) { 84 mA11yTutorialDialogShown = 85 savedInstanceState.getBoolean(KEY_SHOW_A11Y_TUTORIAL_DIALOG, false); 86 if (mA11yTutorialDialogShown) { 87 AccessibilityGestureNavigationTutorial.showGestureNavigationTutorialDialog( 88 getContext(), dialog -> mA11yTutorialDialogShown = false); 89 } 90 } 91 } 92 93 @Override onSaveInstanceState(Bundle outState)94 public void onSaveInstanceState(Bundle outState) { 95 outState.putBoolean(KEY_SHOW_A11Y_TUTORIAL_DIALOG, mA11yTutorialDialogShown); 96 super.onSaveInstanceState(outState); 97 } 98 99 @Override onAttach(Context context)100 public void onAttach(Context context) { 101 super.onAttach(context); 102 103 SuggestionFeatureProvider suggestionFeatureProvider = FeatureFactory.getFactory(context) 104 .getSuggestionFeatureProvider(context); 105 SharedPreferences prefs = suggestionFeatureProvider.getSharedPrefs(context); 106 prefs.edit().putBoolean(PREF_KEY_SUGGESTION_COMPLETE, true).apply(); 107 108 mOverlayManager = IOverlayManager.Stub.asInterface( 109 ServiceManager.getService(Context.OVERLAY_SERVICE)); 110 111 mVideoPreference = new IllustrationPreference(context); 112 setIllustrationVideo(mVideoPreference, getDefaultKey()); 113 114 migrateOverlaySensitivityToSettings(context, mOverlayManager); 115 } 116 117 @Override getMetricsCategory()118 public int getMetricsCategory() { 119 return SettingsEnums.SETTINGS_GESTURE_SWIPE_UP; 120 } 121 122 @Override updateCandidates()123 public void updateCandidates() { 124 final String defaultKey = getDefaultKey(); 125 final String systemDefaultKey = getSystemDefaultKey(); 126 final PreferenceScreen screen = getPreferenceScreen(); 127 screen.removeAll(); 128 screen.addPreference(mVideoPreference); 129 130 final List<? extends CandidateInfo> candidateList = getCandidates(); 131 if (candidateList == null) { 132 return; 133 } 134 for (CandidateInfo info : candidateList) { 135 RadioButtonPreference pref = 136 new RadioButtonPreference(getPrefContext()); 137 bindPreference(pref, info.getKey(), info, defaultKey); 138 bindPreferenceExtra(pref, info.getKey(), info, defaultKey, systemDefaultKey); 139 screen.addPreference(pref); 140 } 141 mayCheckOnlyRadioButton(); 142 } 143 144 @Override bindPreferenceExtra(RadioButtonPreference pref, String key, CandidateInfo info, String defaultKey, String systemDefaultKey)145 public void bindPreferenceExtra(RadioButtonPreference pref, 146 String key, CandidateInfo info, String defaultKey, String systemDefaultKey) { 147 if (!(info instanceof CandidateInfoExtra)) { 148 return; 149 } 150 151 pref.setSummary(((CandidateInfoExtra) info).loadSummary()); 152 153 if (info.getKey() == KEY_SYSTEM_NAV_GESTURAL) { 154 pref.setExtraWidgetOnClickListener((v) -> startActivity(new Intent( 155 GestureNavigationSettingsFragment.GESTURE_NAVIGATION_SETTINGS))); 156 } 157 } 158 159 @Override getPreferenceScreenResId()160 protected int getPreferenceScreenResId() { 161 return R.xml.system_navigation_gesture_settings; 162 } 163 164 @Override getCandidates()165 protected List<? extends CandidateInfo> getCandidates() { 166 final Context c = getContext(); 167 List<CandidateInfoExtra> candidates = new ArrayList<>(); 168 169 if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c, 170 NAV_BAR_MODE_GESTURAL_OVERLAY)) { 171 candidates.add(new CandidateInfoExtra( 172 c.getText(R.string.edge_to_edge_navigation_title), 173 c.getText(R.string.edge_to_edge_navigation_summary), 174 KEY_SYSTEM_NAV_GESTURAL, true /* enabled */)); 175 } 176 if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c, 177 NAV_BAR_MODE_2BUTTON_OVERLAY)) { 178 candidates.add(new CandidateInfoExtra( 179 c.getText(R.string.swipe_up_to_switch_apps_title), 180 c.getText(R.string.swipe_up_to_switch_apps_summary), 181 KEY_SYSTEM_NAV_2BUTTONS, true /* enabled */)); 182 } 183 if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c, 184 NAV_BAR_MODE_3BUTTON_OVERLAY)) { 185 candidates.add(new CandidateInfoExtra( 186 c.getText(R.string.legacy_navigation_title), 187 c.getText(R.string.legacy_navigation_summary), 188 KEY_SYSTEM_NAV_3BUTTONS, true /* enabled */)); 189 } 190 191 return candidates; 192 } 193 194 @Override getDefaultKey()195 protected String getDefaultKey() { 196 return getCurrentSystemNavigationMode(getContext()); 197 } 198 199 @Override setDefaultKey(String key)200 protected boolean setDefaultKey(String key) { 201 setCurrentSystemNavigationMode(mOverlayManager, key); 202 setIllustrationVideo(mVideoPreference, key); 203 setGestureNavigationTutorialDialog(key); 204 return true; 205 } 206 migrateOverlaySensitivityToSettings(Context context, IOverlayManager overlayManager)207 static void migrateOverlaySensitivityToSettings(Context context, 208 IOverlayManager overlayManager) { 209 if (!SystemNavigationPreferenceController.isGestureNavigationEnabled(context)) { 210 return; 211 } 212 213 OverlayInfo info = null; 214 try { 215 info = overlayManager.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT); 216 } catch (RemoteException e) { /* Do nothing */ } 217 if (info != null && !info.isEnabled()) { 218 // Enable the default gesture nav overlay. Back sensitivity for left and right are 219 // stored as separate settings values, and other gesture nav overlays are deprecated. 220 setCurrentSystemNavigationMode(overlayManager, KEY_SYSTEM_NAV_GESTURAL); 221 Settings.Secure.putFloat(context.getContentResolver(), 222 Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f); 223 Settings.Secure.putFloat(context.getContentResolver(), 224 Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f); 225 } 226 } 227 228 @VisibleForTesting getCurrentSystemNavigationMode(Context context)229 static String getCurrentSystemNavigationMode(Context context) { 230 if (SystemNavigationPreferenceController.isGestureNavigationEnabled(context)) { 231 return KEY_SYSTEM_NAV_GESTURAL; 232 } else if (SystemNavigationPreferenceController.is2ButtonNavigationEnabled(context)) { 233 return KEY_SYSTEM_NAV_2BUTTONS; 234 } else { 235 return KEY_SYSTEM_NAV_3BUTTONS; 236 } 237 } 238 239 @VisibleForTesting setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key)240 static void setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key) { 241 String overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY; 242 switch (key) { 243 case KEY_SYSTEM_NAV_GESTURAL: 244 overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY; 245 break; 246 case KEY_SYSTEM_NAV_2BUTTONS: 247 overlayPackage = NAV_BAR_MODE_2BUTTON_OVERLAY; 248 break; 249 case KEY_SYSTEM_NAV_3BUTTONS: 250 overlayPackage = NAV_BAR_MODE_3BUTTON_OVERLAY; 251 break; 252 } 253 254 try { 255 overlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT); 256 } catch (RemoteException e) { 257 throw e.rethrowFromSystemServer(); 258 } 259 } 260 setIllustrationVideo(IllustrationPreference videoPref, String systemNavKey)261 private static void setIllustrationVideo(IllustrationPreference videoPref, 262 String systemNavKey) { 263 switch (systemNavKey) { 264 case KEY_SYSTEM_NAV_GESTURAL: 265 videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_fully_gestural); 266 break; 267 case KEY_SYSTEM_NAV_2BUTTONS: 268 videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_2_button); 269 break; 270 case KEY_SYSTEM_NAV_3BUTTONS: 271 videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_3_button); 272 break; 273 } 274 } 275 setGestureNavigationTutorialDialog(String systemNavKey)276 private void setGestureNavigationTutorialDialog(String systemNavKey) { 277 if (TextUtils.equals(KEY_SYSTEM_NAV_GESTURAL, systemNavKey) 278 && !isAccessibilityFloatingMenuEnabled() 279 && (isAnyServiceSupportAccessibilityButton() || isNavBarMagnificationEnabled())) { 280 mA11yTutorialDialogShown = true; 281 AccessibilityGestureNavigationTutorial.showGestureNavigationTutorialDialog(getContext(), 282 dialog -> mA11yTutorialDialogShown = false); 283 } else { 284 mA11yTutorialDialogShown = false; 285 } 286 } 287 isAnyServiceSupportAccessibilityButton()288 private boolean isAnyServiceSupportAccessibilityButton() { 289 final AccessibilityManager ams = getContext().getSystemService(AccessibilityManager.class); 290 final List<String> targets = ams.getAccessibilityShortcutTargets( 291 AccessibilityManager.ACCESSIBILITY_BUTTON); 292 return !targets.isEmpty(); 293 } 294 isNavBarMagnificationEnabled()295 private boolean isNavBarMagnificationEnabled() { 296 return Settings.Secure.getInt(getContext().getContentResolver(), 297 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1; 298 } 299 isAccessibilityFloatingMenuEnabled()300 private boolean isAccessibilityFloatingMenuEnabled() { 301 return Settings.Secure.getInt(getContext().getContentResolver(), 302 Settings.Secure.ACCESSIBILITY_BUTTON_MODE, /* def= */ -1) 303 == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; 304 } 305 306 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 307 new BaseSearchIndexProvider(R.xml.system_navigation_gesture_settings) { 308 309 @Override 310 protected boolean isPageSearchEnabled(Context context) { 311 return SystemNavigationPreferenceController.isGestureAvailable(context); 312 } 313 }; 314 315 // From HelpResourceProvider 316 @Override getHelpResource()317 public int getHelpResource() { 318 // TODO(b/146001201): Replace with system navigation help page when ready. 319 return R.string.help_uri_default; 320 } 321 } 322