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