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