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.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; 22 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY; 23 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; 24 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY; 25 26 import android.app.settings.SettingsEnums; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.SharedPreferences; 30 import android.content.om.IOverlayManager; 31 import android.content.om.OverlayInfo; 32 import android.content.res.Resources; 33 import android.os.Bundle; 34 import android.os.RemoteException; 35 import android.os.ServiceManager; 36 import android.provider.Settings; 37 import android.text.TextUtils; 38 import android.view.accessibility.AccessibilityManager; 39 40 import androidx.annotation.Nullable; 41 import androidx.annotation.VisibleForTesting; 42 import androidx.preference.PreferenceScreen; 43 44 import com.android.settings.R; 45 import com.android.settings.accessibility.AccessibilityGestureNavigationTutorial; 46 import com.android.settings.core.SubSettingLauncher; 47 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; 48 import com.android.settings.overlay.FeatureFactory; 49 import com.android.settings.search.BaseSearchIndexProvider; 50 import com.android.settings.support.actionbar.HelpResourceProvider; 51 import com.android.settings.utils.CandidateInfoExtra; 52 import com.android.settings.widget.RadioButtonPickerFragment; 53 import com.android.settingslib.search.SearchIndexable; 54 import com.android.settingslib.search.SearchIndexableRaw; 55 import com.android.settingslib.widget.CandidateInfo; 56 import com.android.settingslib.widget.IllustrationPreference; 57 import com.android.settingslib.widget.SelectorWithWidgetPreference; 58 59 import java.util.ArrayList; 60 import java.util.List; 61 62 @SearchIndexable 63 public class SystemNavigationGestureSettings extends RadioButtonPickerFragment implements 64 HelpResourceProvider { 65 66 @VisibleForTesting 67 static final String KEY_SYSTEM_NAV_3BUTTONS = "system_nav_3buttons"; 68 @VisibleForTesting 69 static final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons"; 70 @VisibleForTesting 71 static final String KEY_SYSTEM_NAV_GESTURAL = "system_nav_gestural"; 72 73 public static final String PREF_KEY_SUGGESTION_COMPLETE = 74 "pref_system_navigation_suggestion_complete"; 75 76 private static final String KEY_SHOW_A11Y_TUTORIAL_DIALOG = "show_a11y_tutorial_dialog_bool"; 77 78 static final String LAUNCHER_PACKAGE_NAME = "com.google.android.apps.nexuslauncher"; 79 80 static final String ACTION_GESTURE_SANDBOX = "com.android.quickstep.action.GESTURE_SANDBOX"; 81 82 final Intent mLaunchSandboxIntent = new Intent(ACTION_GESTURE_SANDBOX) 83 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 84 .putExtra("use_tutorial_menu", true) 85 .setPackage(LAUNCHER_PACKAGE_NAME); 86 87 private static final int MIN_LARGESCREEN_WIDTH_DP = 600; 88 89 private boolean mA11yTutorialDialogShown = false; 90 91 private IOverlayManager mOverlayManager; 92 93 private IllustrationPreference mVideoPreference; 94 95 @Override onCreate(@ullable Bundle savedInstanceState)96 public void onCreate(@Nullable Bundle savedInstanceState) { 97 super.onCreate(savedInstanceState); 98 if (savedInstanceState != null) { 99 mA11yTutorialDialogShown = 100 savedInstanceState.getBoolean(KEY_SHOW_A11Y_TUTORIAL_DIALOG, false); 101 if (mA11yTutorialDialogShown) { 102 AccessibilityGestureNavigationTutorial.showGestureNavigationTutorialDialog( 103 getContext(), dialog -> mA11yTutorialDialogShown = false); 104 } 105 } 106 } 107 108 @Override onSaveInstanceState(Bundle outState)109 public void onSaveInstanceState(Bundle outState) { 110 outState.putBoolean(KEY_SHOW_A11Y_TUTORIAL_DIALOG, mA11yTutorialDialogShown); 111 super.onSaveInstanceState(outState); 112 } 113 114 @Override onAttach(Context context)115 public void onAttach(Context context) { 116 super.onAttach(context); 117 118 SuggestionFeatureProvider suggestionFeatureProvider = 119 FeatureFactory.getFactory(context).getSuggestionFeatureProvider(); 120 SharedPreferences prefs = suggestionFeatureProvider.getSharedPrefs(context); 121 prefs.edit().putBoolean(PREF_KEY_SUGGESTION_COMPLETE, true).apply(); 122 123 mOverlayManager = IOverlayManager.Stub.asInterface( 124 ServiceManager.getService(Context.OVERLAY_SERVICE)); 125 126 mVideoPreference = new IllustrationPreference(context); 127 Context windowContext = context.createWindowContext(TYPE_APPLICATION_OVERLAY, null); 128 if (windowContext.getResources() 129 .getConfiguration().smallestScreenWidthDp >= MIN_LARGESCREEN_WIDTH_DP) { 130 mVideoPreference.applyDynamicColor(); 131 } 132 setIllustrationVideo(mVideoPreference, getDefaultKey()); 133 setIllustrationClickListener(mVideoPreference, getDefaultKey()); 134 135 migrateOverlaySensitivityToSettings(context, mOverlayManager); 136 } 137 138 @Override getMetricsCategory()139 public int getMetricsCategory() { 140 return SettingsEnums.SETTINGS_GESTURE_SWIPE_UP; 141 } 142 143 @Override updateCandidates()144 public void updateCandidates() { 145 final String defaultKey = getDefaultKey(); 146 final String systemDefaultKey = getSystemDefaultKey(); 147 final PreferenceScreen screen = getPreferenceScreen(); 148 screen.removeAll(); 149 screen.addPreference(mVideoPreference); 150 151 final List<? extends CandidateInfo> candidateList = getCandidates(); 152 if (candidateList == null) { 153 return; 154 } 155 for (CandidateInfo info : candidateList) { 156 SelectorWithWidgetPreference pref = 157 new SelectorWithWidgetPreference(getPrefContext()); 158 bindPreference(pref, info.getKey(), info, defaultKey); 159 bindPreferenceExtra(pref, info.getKey(), info, defaultKey, systemDefaultKey); 160 screen.addPreference(pref); 161 } 162 mayCheckOnlyRadioButton(); 163 } 164 165 @Override bindPreferenceExtra(SelectorWithWidgetPreference pref, String key, CandidateInfo info, String defaultKey, String systemDefaultKey)166 public void bindPreferenceExtra(SelectorWithWidgetPreference pref, 167 String key, CandidateInfo info, String defaultKey, String systemDefaultKey) { 168 if (!(info instanceof CandidateInfoExtra)) { 169 return; 170 } 171 172 pref.setSummary(((CandidateInfoExtra) info).loadSummary()); 173 174 if (KEY_SYSTEM_NAV_GESTURAL.equals(info.getKey())) { 175 pref.setExtraWidgetOnClickListener((v) -> startActivity(new Intent( 176 GestureNavigationSettingsFragment.GESTURE_NAVIGATION_SETTINGS))); 177 } 178 179 if (KEY_SYSTEM_NAV_2BUTTONS.equals(info.getKey()) || KEY_SYSTEM_NAV_3BUTTONS.equals( 180 info.getKey())) { 181 pref.setExtraWidgetOnClickListener((v) -> 182 new SubSettingLauncher(getContext()) 183 .setDestination(ButtonNavigationSettingsFragment.class.getName()) 184 .setSourceMetricsCategory(SettingsEnums.SETTINGS_GESTURE_SWIPE_UP) 185 .launch()); 186 } 187 } 188 189 @Override getPreferenceScreenResId()190 protected int getPreferenceScreenResId() { 191 return R.xml.system_navigation_gesture_settings; 192 } 193 194 @Override getCandidates()195 protected List<? extends CandidateInfo> getCandidates() { 196 final Context c = getContext(); 197 List<CandidateInfoExtra> candidates = new ArrayList<>(); 198 199 if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c, 200 NAV_BAR_MODE_GESTURAL_OVERLAY)) { 201 candidates.add(new CandidateInfoExtra( 202 c.getText(R.string.edge_to_edge_navigation_title), 203 c.getText(R.string.edge_to_edge_navigation_summary), 204 KEY_SYSTEM_NAV_GESTURAL, true /* enabled */)); 205 } 206 if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c, 207 NAV_BAR_MODE_2BUTTON_OVERLAY)) { 208 candidates.add(new CandidateInfoExtra( 209 c.getText(R.string.swipe_up_to_switch_apps_title), 210 c.getText(R.string.swipe_up_to_switch_apps_summary), 211 KEY_SYSTEM_NAV_2BUTTONS, true /* enabled */)); 212 } 213 if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c, 214 NAV_BAR_MODE_3BUTTON_OVERLAY)) { 215 candidates.add(new CandidateInfoExtra( 216 c.getText(R.string.legacy_navigation_title), 217 c.getText(R.string.legacy_navigation_summary), 218 KEY_SYSTEM_NAV_3BUTTONS, true /* enabled */)); 219 } 220 221 return candidates; 222 } 223 224 @Override getDefaultKey()225 protected String getDefaultKey() { 226 return getCurrentSystemNavigationMode(getContext()); 227 } 228 229 @Override setDefaultKey(String key)230 protected boolean setDefaultKey(String key) { 231 setCurrentSystemNavigationMode(mOverlayManager, key); 232 setIllustrationVideo(mVideoPreference, key); 233 setGestureNavigationTutorialDialog(key); 234 setIllustrationClickListener(mVideoPreference, key); 235 return true; 236 } 237 isGestureTutorialAvailable()238 private boolean isGestureTutorialAvailable() { 239 Context context = getContext(); 240 return context != null 241 && mLaunchSandboxIntent.resolveActivity(context.getPackageManager()) != null; 242 } 243 setIllustrationClickListener(IllustrationPreference videoPref, String systemNavKey)244 private void setIllustrationClickListener(IllustrationPreference videoPref, 245 String systemNavKey) { 246 247 switch (systemNavKey) { 248 case KEY_SYSTEM_NAV_GESTURAL: 249 if (isGestureTutorialAvailable()){ 250 videoPref.setOnPreferenceClickListener(preference -> { 251 startActivity(mLaunchSandboxIntent); 252 return true; 253 }); 254 } else { 255 videoPref.setOnPreferenceClickListener(null); 256 } 257 258 break; 259 case KEY_SYSTEM_NAV_2BUTTONS: 260 case KEY_SYSTEM_NAV_3BUTTONS: 261 default: 262 videoPref.setOnPreferenceClickListener(null); 263 break; 264 } 265 } 266 migrateOverlaySensitivityToSettings(Context context, IOverlayManager overlayManager)267 static void migrateOverlaySensitivityToSettings(Context context, 268 IOverlayManager overlayManager) { 269 if (!SystemNavigationPreferenceController.isGestureNavigationEnabled(context)) { 270 return; 271 } 272 273 OverlayInfo info = null; 274 try { 275 info = overlayManager.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT); 276 } catch (RemoteException e) { /* Do nothing */ } 277 if (info != null && !info.isEnabled()) { 278 // Enable the default gesture nav overlay. Back sensitivity for left and right are 279 // stored as separate settings values, and other gesture nav overlays are deprecated. 280 setCurrentSystemNavigationMode(overlayManager, KEY_SYSTEM_NAV_GESTURAL); 281 Settings.Secure.putFloat(context.getContentResolver(), 282 Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f); 283 Settings.Secure.putFloat(context.getContentResolver(), 284 Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f); 285 } 286 } 287 288 @VisibleForTesting getCurrentSystemNavigationMode(Context context)289 static String getCurrentSystemNavigationMode(Context context) { 290 if (SystemNavigationPreferenceController.isGestureNavigationEnabled(context)) { 291 return KEY_SYSTEM_NAV_GESTURAL; 292 } else if (SystemNavigationPreferenceController.is2ButtonNavigationEnabled(context)) { 293 return KEY_SYSTEM_NAV_2BUTTONS; 294 } else { 295 return KEY_SYSTEM_NAV_3BUTTONS; 296 } 297 } 298 299 @VisibleForTesting setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key)300 static void setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key) { 301 String overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY; 302 switch (key) { 303 case KEY_SYSTEM_NAV_GESTURAL: 304 overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY; 305 break; 306 case KEY_SYSTEM_NAV_2BUTTONS: 307 overlayPackage = NAV_BAR_MODE_2BUTTON_OVERLAY; 308 break; 309 case KEY_SYSTEM_NAV_3BUTTONS: 310 overlayPackage = NAV_BAR_MODE_3BUTTON_OVERLAY; 311 break; 312 } 313 314 try { 315 overlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT); 316 } catch (RemoteException e) { 317 throw e.rethrowFromSystemServer(); 318 } 319 } 320 setIllustrationVideo(IllustrationPreference videoPref, String systemNavKey)321 private void setIllustrationVideo(IllustrationPreference videoPref, 322 String systemNavKey) { 323 switch (systemNavKey) { 324 case KEY_SYSTEM_NAV_GESTURAL: 325 if (isGestureTutorialAvailable()) { 326 videoPref.setLottieAnimationResId( 327 R.raw.lottie_system_nav_fully_gestural_with_nav); 328 } else { 329 videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_fully_gestural); 330 } 331 break; 332 case KEY_SYSTEM_NAV_2BUTTONS: 333 videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_2_button); 334 break; 335 case KEY_SYSTEM_NAV_3BUTTONS: 336 videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_3_button); 337 break; 338 } 339 } 340 setGestureNavigationTutorialDialog(String systemNavKey)341 private void setGestureNavigationTutorialDialog(String systemNavKey) { 342 if (TextUtils.equals(KEY_SYSTEM_NAV_GESTURAL, systemNavKey) 343 && !isAccessibilityFloatingMenuEnabled() 344 && (isAnyServiceSupportAccessibilityButton() || isNavBarMagnificationEnabled())) { 345 mA11yTutorialDialogShown = true; 346 AccessibilityGestureNavigationTutorial.showGestureNavigationTutorialDialog(getContext(), 347 dialog -> mA11yTutorialDialogShown = false); 348 } else { 349 mA11yTutorialDialogShown = false; 350 } 351 } 352 isAnyServiceSupportAccessibilityButton()353 private boolean isAnyServiceSupportAccessibilityButton() { 354 final AccessibilityManager ams = getContext().getSystemService(AccessibilityManager.class); 355 final List<String> targets = ams.getAccessibilityShortcutTargets( 356 AccessibilityManager.ACCESSIBILITY_BUTTON); 357 return !targets.isEmpty(); 358 } 359 isNavBarMagnificationEnabled()360 private boolean isNavBarMagnificationEnabled() { 361 return Settings.Secure.getInt(getContext().getContentResolver(), 362 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1; 363 } 364 isAccessibilityFloatingMenuEnabled()365 private boolean isAccessibilityFloatingMenuEnabled() { 366 return Settings.Secure.getInt(getContext().getContentResolver(), 367 Settings.Secure.ACCESSIBILITY_BUTTON_MODE, /* def= */ -1) 368 == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; 369 } 370 371 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 372 new BaseSearchIndexProvider(R.xml.system_navigation_gesture_settings) { 373 374 @Override 375 protected boolean isPageSearchEnabled(Context context) { 376 return SystemNavigationPreferenceController.isGestureAvailable(context); 377 } 378 379 @Override 380 public List<SearchIndexableRaw> getRawDataToIndex(Context context, 381 boolean enabled) { 382 final Resources res = context.getResources(); 383 final List<SearchIndexableRaw> result = new ArrayList<>(); 384 385 if (SystemNavigationPreferenceController.isOverlayPackageAvailable(context, 386 NAV_BAR_MODE_GESTURAL_OVERLAY)) { 387 SearchIndexableRaw data = new SearchIndexableRaw(context); 388 data.title = res.getString(R.string.edge_to_edge_navigation_title); 389 data.key = KEY_SYSTEM_NAV_GESTURAL; 390 result.add(data); 391 } 392 393 if (SystemNavigationPreferenceController.isOverlayPackageAvailable(context, 394 NAV_BAR_MODE_2BUTTON_OVERLAY)) { 395 SearchIndexableRaw data = new SearchIndexableRaw(context); 396 data.title = res.getString(R.string.swipe_up_to_switch_apps_title); 397 data.key = KEY_SYSTEM_NAV_2BUTTONS; 398 result.add(data); 399 } 400 401 if (SystemNavigationPreferenceController.isOverlayPackageAvailable(context, 402 NAV_BAR_MODE_3BUTTON_OVERLAY)) { 403 SearchIndexableRaw data = new SearchIndexableRaw(context); 404 data.title = res.getString(R.string.legacy_navigation_title); 405 data.key = KEY_SYSTEM_NAV_3BUTTONS; 406 result.add(data); 407 } 408 409 return result; 410 } 411 }; 412 413 // From HelpResourceProvider 414 @Override getHelpResource()415 public int getHelpResource() { 416 // TODO(b/146001201): Replace with system navigation help page when ready. 417 return R.string.help_uri_default; 418 } 419 } 420