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