1 /* 2 * Copyright (C) 2020 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 com.android.internal.accessibility.AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME; 20 21 import android.content.ContentResolver; 22 import android.content.Context; 23 import android.database.ContentObserver; 24 import android.net.Uri; 25 import android.os.Handler; 26 import android.os.Looper; 27 import android.os.SystemProperties; 28 import android.os.UserHandle; 29 import android.provider.Settings; 30 import android.text.TextUtils; 31 32 import androidx.annotation.VisibleForTesting; 33 34 /** 35 * The Util to query one-handed mode settings config 36 */ 37 public class OneHandedSettingsUtils { 38 39 static final String ONE_HANDED_MODE_TARGET_NAME = 40 ONE_HANDED_COMPONENT_NAME.getShortClassName(); 41 42 static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode"; 43 static final int OFF = 0; 44 static final int ON = 1; 45 static final Uri ONE_HANDED_MODE_ENABLED_URI = 46 Settings.Secure.getUriFor(Settings.Secure.ONE_HANDED_MODE_ENABLED); 47 static final Uri SHOW_NOTIFICATION_ENABLED_URI = 48 Settings.Secure.getUriFor(Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED); 49 static final Uri SOFTWARE_SHORTCUT_ENABLED_URI = 50 Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); 51 static final Uri HARDWARE_SHORTCUT_ENABLED_URI = 52 Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); 53 static final Uri QS_SHORTCUT_ENABLED_URI = 54 Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_QS_TARGETS); 55 56 public enum OneHandedTimeout { 57 NEVER(0), SHORT(4), MEDIUM(8), LONG(12); 58 59 private final int mValue; 60 OneHandedTimeout(int value)61 OneHandedTimeout(int value) { 62 this.mValue = value; 63 } 64 getValue()65 public int getValue() { 66 return mValue; 67 } 68 } 69 70 private final Context mContext; 71 private final SettingsObserver mSettingsObserver; 72 73 private static int sCurrentUserId; 74 OneHandedSettingsUtils(Context context)75 OneHandedSettingsUtils(Context context) { 76 mContext = context; 77 sCurrentUserId = UserHandle.myUserId(); 78 mSettingsObserver = new SettingsObserver(new Handler(Looper.getMainLooper())); 79 } 80 81 /** 82 * Gets One-Handed mode support flag. 83 */ isSupportOneHandedMode()84 public static boolean isSupportOneHandedMode() { 85 return SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false); 86 } 87 88 /** 89 * Gets one-handed mode feature enable or disable flag from Settings provider. 90 * 91 * @param context App context 92 * @return enable or disable one-handed mode flag. 93 */ isOneHandedModeEnabled(Context context)94 public static boolean isOneHandedModeEnabled(Context context) { 95 return Settings.Secure.getIntForUser(context.getContentResolver(), 96 Settings.Secure.ONE_HANDED_MODE_ENABLED, OFF, sCurrentUserId) == ON; 97 } 98 99 /** 100 * Sets one-handed mode enable or disable flag to Settings provider. 101 * 102 * @param context App context 103 * @param enable enable or disable one-handed mode. 104 */ setOneHandedModeEnabled(Context context, boolean enable)105 public static void setOneHandedModeEnabled(Context context, boolean enable) { 106 Settings.Secure.putIntForUser(context.getContentResolver(), 107 Settings.Secure.ONE_HANDED_MODE_ENABLED, enable ? ON : OFF, sCurrentUserId); 108 } 109 110 /** 111 * Gets enabling taps app to exit one-handed mode flag from Settings provider. 112 * 113 * @param context App context 114 * @return enable or disable taps app to exit. 115 */ isTapsAppToExitEnabled(Context context)116 public static boolean isTapsAppToExitEnabled(Context context) { 117 return Settings.Secure.getIntForUser(context.getContentResolver(), 118 Settings.Secure.TAPS_APP_TO_EXIT, OFF, sCurrentUserId) == ON; 119 } 120 121 /** 122 * Sets enabling taps app to exit one-handed mode flag to Settings provider. 123 * 124 * @param context App context 125 * @param enable enable or disable when taping app to exit one-handed mode. 126 */ setTapsAppToExitEnabled(Context context, boolean enable)127 public static boolean setTapsAppToExitEnabled(Context context, boolean enable) { 128 return Settings.Secure.putIntForUser(context.getContentResolver(), 129 Settings.Secure.TAPS_APP_TO_EXIT, enable ? ON : OFF, sCurrentUserId); 130 } 131 132 /** 133 * Gets one-handed mode timeout value from Settings provider. 134 * 135 * @param context App context 136 * @return timeout value in seconds. 137 */ getTimeoutValue(Context context)138 public static int getTimeoutValue(Context context) { 139 return Settings.Secure.getIntForUser(context.getContentResolver(), 140 Settings.Secure.ONE_HANDED_MODE_TIMEOUT, 141 OneHandedTimeout.MEDIUM.getValue() /* default MEDIUM(8) by UX */, 142 sCurrentUserId); 143 } 144 145 /** 146 * Gets current user id from OneHandedSettingsUtils 147 * 148 * @return the current user id in OneHandedSettingsUtils 149 */ getUserId()150 public static int getUserId() { 151 return sCurrentUserId; 152 } 153 154 /** 155 * Sets specific user id for OneHandedSettingsUtils 156 * 157 * @param userId the user id to be updated 158 */ setUserId(int userId)159 public static void setUserId(int userId) { 160 sCurrentUserId = userId; 161 } 162 163 /** 164 * Sets one-handed mode timeout value to Settings provider. 165 * 166 * @param context App context 167 * @param timeout timeout in seconds for exiting one-handed mode. 168 */ setTimeoutValue(Context context, int timeout)169 public static void setTimeoutValue(Context context, int timeout) { 170 Settings.Secure.putIntForUser(context.getContentResolver(), 171 Settings.Secure.ONE_HANDED_MODE_TIMEOUT, timeout, sCurrentUserId); 172 } 173 174 /** 175 * Gets Swipe-down-notification enable or disable flag from Settings provider. 176 * 177 * @param context App context 178 * @return enable or disable Swipe-down-notification flag. 179 */ isSwipeDownNotificationEnabled(Context context)180 public static boolean isSwipeDownNotificationEnabled(Context context) { 181 return Settings.Secure.getIntForUser(context.getContentResolver(), 182 Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, OFF, sCurrentUserId) == ON; 183 } 184 185 /** 186 * Sets Swipe-down-notification enable or disable flag to Settings provider. 187 * 188 * @param context App context 189 * @param enable enable or disable Swipe-down-notification. 190 */ setSwipeDownNotificationEnabled(Context context, boolean enable)191 public static void setSwipeDownNotificationEnabled(Context context, boolean enable) { 192 Settings.Secure.putIntForUser(context.getContentResolver(), 193 Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, enable ? ON : OFF, 194 sCurrentUserId); 195 } 196 197 /** 198 * Set NavigationBar mode flag to Settings provider. 199 * @param context App context 200 * @param value Navigation bar mode: 201 * 0 = 3 button 202 * 1 = 2 button 203 * 2 = fully gestural 204 * @return true if the value was set, false on database errors. 205 */ 206 @VisibleForTesting setNavigationBarMode(Context context, String value)207 public boolean setNavigationBarMode(Context context, String value) { 208 return Settings.Secure.putStringForUser(context.getContentResolver(), 209 Settings.Secure.NAVIGATION_MODE, value, UserHandle.myUserId()); 210 } 211 212 /** 213 * Get NavigationBar mode flag from Settings provider. 214 * @param context App context 215 * @return Navigation bar mode: 216 * 0 = 3 button 217 * 1 = 2 button 218 * 2 = fully gestural 219 */ getNavigationBarMode(Context context)220 public static int getNavigationBarMode(Context context) { 221 return Settings.Secure.getIntForUser(context.getContentResolver(), 222 Settings.Secure.NAVIGATION_MODE, 2 /* fully gestural */, sCurrentUserId); 223 } 224 225 /** 226 * Check if One-handed mode settings controllers can enabled or disabled. 227 * @param context App context 228 * @return true if controllers are able to enabled, false otherwise. 229 * 230 * Note: For better UX experience, just disabled controls that let users know to use 231 * this feature, they need to make sure gesture navigation is turned on in system 232 * navigation settings. 233 */ canEnableController(Context context)234 public static boolean canEnableController(Context context) { 235 return ((OneHandedSettingsUtils.isOneHandedModeEnabled(context) 236 && getNavigationBarMode(context) != 0 /* 3-button */) 237 || getShortcutEnabled(context)); 238 } 239 240 /** 241 * Queries one-handed mode shortcut enabled in settings or not. 242 * 243 * @return true if user enabled one-handed shortcut in settings, false otherwise. 244 */ getShortcutEnabled(Context context)245 public static boolean getShortcutEnabled(Context context) { 246 // Checks SOFTWARE_SHORTCUT_KEY 247 final String targetsSW = Settings.Secure.getStringForUser(context.getContentResolver(), 248 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, sCurrentUserId); 249 if (!TextUtils.isEmpty(targetsSW) && targetsSW.contains(ONE_HANDED_MODE_TARGET_NAME)) { 250 return true; 251 } 252 253 // Checks HARDWARE_SHORTCUT_KEY 254 final String targetsHW = Settings.Secure.getStringForUser(context.getContentResolver(), 255 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, sCurrentUserId); 256 if (!TextUtils.isEmpty(targetsHW) && targetsHW.contains(ONE_HANDED_MODE_TARGET_NAME)) { 257 return true; 258 } 259 260 if (android.view.accessibility.Flags.a11yQsShortcut()) { 261 // Checks QS_SHORTCUT_KEY 262 final String targetsQs = Settings.Secure.getStringForUser(context.getContentResolver(), 263 Settings.Secure.ACCESSIBILITY_QS_TARGETS, sCurrentUserId); 264 if (!TextUtils.isEmpty(targetsQs) && targetsQs.contains(ONE_HANDED_MODE_TARGET_NAME)) { 265 return true; 266 } 267 } 268 269 return false; 270 } 271 272 /** 273 * This is a test only API for set Shortcut enabled or not. 274 */ 275 @VisibleForTesting setShortcutEnabled(Context context, boolean enabled)276 public void setShortcutEnabled(Context context, boolean enabled) { 277 final String targetName = enabled ? ONE_HANDED_MODE_TARGET_NAME : ""; 278 Settings.Secure.putStringForUser(context.getContentResolver(), 279 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, targetName, sCurrentUserId); 280 } 281 282 /** 283 * Registers callback for observing Settings.Secure.ONE_HANDED_MODE_ENABLED state. 284 * @param callback for state changes 285 */ registerToggleAwareObserver(TogglesCallback callback)286 public void registerToggleAwareObserver(TogglesCallback callback) { 287 mSettingsObserver.observe(); 288 mSettingsObserver.setCallback(callback); 289 } 290 291 /** 292 * Unregisters callback for observing Settings.Secure.ONE_HANDED_MODE_ENABLED state. 293 */ unregisterToggleAwareObserver()294 public void unregisterToggleAwareObserver() { 295 final ContentResolver resolver = mContext.getContentResolver(); 296 resolver.unregisterContentObserver(mSettingsObserver); 297 } 298 299 private final class SettingsObserver extends ContentObserver { 300 private TogglesCallback mCallback; 301 SettingsObserver(Handler handler)302 SettingsObserver(Handler handler) { 303 super(handler); 304 } 305 setCallback(TogglesCallback callback)306 private void setCallback(TogglesCallback callback) { 307 mCallback = callback; 308 } 309 observe()310 public void observe() { 311 final ContentResolver resolver = mContext.getContentResolver(); 312 resolver.registerContentObserver(ONE_HANDED_MODE_ENABLED_URI, true, this); 313 resolver.registerContentObserver(SHOW_NOTIFICATION_ENABLED_URI, true, this); 314 resolver.registerContentObserver(SOFTWARE_SHORTCUT_ENABLED_URI, true, this); 315 resolver.registerContentObserver(HARDWARE_SHORTCUT_ENABLED_URI, true, this); 316 if (android.view.accessibility.Flags.a11yQsShortcut()) { 317 resolver.registerContentObserver(QS_SHORTCUT_ENABLED_URI, true, this); 318 } 319 } 320 321 @Override onChange(boolean selfChange, Uri uri)322 public void onChange(boolean selfChange, Uri uri) { 323 if (mCallback != null) mCallback.onChange(uri); 324 } 325 } 326 327 /** 328 * An interface for when Settings.Secure key state changes. 329 */ 330 public interface TogglesCallback { 331 /** 332 * Callback method for Settings.Secure key state changes. 333 * 334 * @param uri The Uri of the changed content. 335 */ onChange(Uri uri)336 void onChange(Uri uri); 337 } 338 } 339