1 /* 2 * Copyright (C) 2022 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.systemui.accessibility.floatingmenu; 18 19 import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED; 20 import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT; 21 import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY; 22 import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE; 23 import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES; 24 25 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE; 26 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets; 27 import static com.android.systemui.accessibility.floatingmenu.MenuFadeEffectInfoKt.DEFAULT_FADE_EFFECT_IS_ENABLED; 28 import static com.android.systemui.accessibility.floatingmenu.MenuFadeEffectInfoKt.DEFAULT_OPACITY_VALUE; 29 import static com.android.systemui.accessibility.floatingmenu.MenuViewAppearance.MenuSizeType.SMALL; 30 31 import android.annotation.FloatRange; 32 import android.annotation.IntDef; 33 import android.content.ComponentCallbacks; 34 import android.content.Context; 35 import android.content.pm.ActivityInfo; 36 import android.content.res.Configuration; 37 import android.database.ContentObserver; 38 import android.os.Build; 39 import android.os.Handler; 40 import android.os.Looper; 41 import android.os.UserHandle; 42 import android.provider.Settings; 43 import android.text.TextUtils; 44 import android.util.Log; 45 import android.view.View; 46 import android.view.accessibility.AccessibilityManager; 47 48 import androidx.annotation.NonNull; 49 50 import com.android.internal.accessibility.dialog.AccessibilityTarget; 51 import com.android.internal.annotations.VisibleForTesting; 52 import com.android.systemui.Prefs; 53 import com.android.systemui.util.settings.SecureSettings; 54 55 import java.lang.annotation.Retention; 56 import java.lang.annotation.RetentionPolicy; 57 import java.util.List; 58 59 /** 60 * Stores and observe the settings contents for the menu view. 61 */ 62 class MenuInfoRepository { 63 private static final String TAG = "MenuInfoRepository"; 64 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Build.IS_DEBUGGABLE; 65 66 @FloatRange(from = 0.0, to = 1.0) 67 private static final float DEFAULT_MENU_POSITION_X_PERCENT = 1.0f; 68 69 @FloatRange(from = 0.0, to = 1.0) 70 private static final float DEFAULT_MENU_POSITION_X_PERCENT_RTL = 0.0f; 71 72 @FloatRange(from = 0.0, to = 1.0) 73 private static final float DEFAULT_MENU_POSITION_Y_PERCENT = 0.77f; 74 private static final boolean DEFAULT_MOVE_TO_TUCKED_VALUE = false; 75 private static final boolean DEFAULT_HAS_SEEN_DOCK_TOOLTIP_VALUE = false; 76 private static final int DEFAULT_MIGRATION_TOOLTIP_VALUE_PROMPT = MigrationPrompt.DISABLED; 77 78 private final Context mContext; 79 private final Configuration mConfiguration; 80 private final AccessibilityManager mAccessibilityManager; 81 private final AccessibilityManager.AccessibilityServicesStateChangeListener 82 mA11yServicesStateChangeListener = manager -> onTargetFeaturesChanged(); 83 private final Handler mHandler = new Handler(Looper.getMainLooper()); 84 private final OnSettingsContentsChanged mSettingsContentsCallback; 85 private final SecureSettings mSecureSettings; 86 private Position mPercentagePosition; 87 88 @IntDef({ 89 MigrationPrompt.DISABLED, 90 MigrationPrompt.ENABLED, 91 }) 92 @Retention(RetentionPolicy.SOURCE) 93 @interface MigrationPrompt { 94 int DISABLED = 0; 95 int ENABLED = 1; 96 } 97 98 @VisibleForTesting 99 final ContentObserver mMenuTargetFeaturesContentObserver = 100 new ContentObserver(mHandler) { 101 @Override 102 public void onChange(boolean selfChange) { 103 onTargetFeaturesChanged(); 104 } 105 }; 106 107 @VisibleForTesting 108 final ContentObserver mMenuSizeContentObserver = 109 new ContentObserver(mHandler) { 110 @Override 111 public void onChange(boolean selfChange) { 112 mSettingsContentsCallback.onSizeTypeChanged( 113 getMenuSizeTypeFromSettings()); 114 } 115 }; 116 117 @VisibleForTesting 118 final ContentObserver mMenuFadeOutContentObserver = 119 new ContentObserver(mHandler) { 120 @Override 121 public void onChange(boolean selfChange) { 122 mSettingsContentsCallback.onFadeEffectInfoChanged(getMenuFadeEffectInfo()); 123 } 124 }; 125 126 @VisibleForTesting 127 final ComponentCallbacks mComponentCallbacks = new ComponentCallbacks() { 128 @Override 129 public void onConfigurationChanged(@NonNull Configuration newConfig) { 130 final int diff = newConfig.diff(mConfiguration); 131 132 if (DEBUG) { 133 Log.d(TAG, "onConfigurationChanged = " + Configuration.configurationDiffToString( 134 diff)); 135 } 136 137 if ((diff & ActivityInfo.CONFIG_LOCALE) != 0) { 138 onTargetFeaturesChanged(); 139 } 140 141 mConfiguration.setTo(newConfig); 142 } 143 144 @Override 145 public void onLowMemory() { 146 // Do nothing. 147 } 148 }; 149 MenuInfoRepository(Context context, AccessibilityManager accessibilityManager, OnSettingsContentsChanged settingsContentsChanged, SecureSettings secureSettings)150 MenuInfoRepository(Context context, AccessibilityManager accessibilityManager, 151 OnSettingsContentsChanged settingsContentsChanged, SecureSettings secureSettings) { 152 mContext = context; 153 mAccessibilityManager = accessibilityManager; 154 mConfiguration = new Configuration(context.getResources().getConfiguration()); 155 mSettingsContentsCallback = settingsContentsChanged; 156 mSecureSettings = secureSettings; 157 158 mPercentagePosition = getStartPosition(); 159 } 160 loadMenuMoveToTucked(OnInfoReady<Boolean> callback)161 void loadMenuMoveToTucked(OnInfoReady<Boolean> callback) { 162 callback.onReady( 163 Prefs.getBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, 164 DEFAULT_MOVE_TO_TUCKED_VALUE)); 165 } 166 loadDockTooltipVisibility(OnInfoReady<Boolean> callback)167 void loadDockTooltipVisibility(OnInfoReady<Boolean> callback) { 168 callback.onReady(Prefs.getBoolean(mContext, 169 Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, 170 DEFAULT_HAS_SEEN_DOCK_TOOLTIP_VALUE)); 171 } 172 loadMigrationTooltipVisibility(OnInfoReady<Boolean> callback)173 void loadMigrationTooltipVisibility(OnInfoReady<Boolean> callback) { 174 callback.onReady(mSecureSettings.getIntForUser( 175 ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, 176 DEFAULT_MIGRATION_TOOLTIP_VALUE_PROMPT, UserHandle.USER_CURRENT) 177 == MigrationPrompt.ENABLED); 178 } 179 loadMenuPosition(OnInfoReady<Position> callback)180 void loadMenuPosition(OnInfoReady<Position> callback) { 181 callback.onReady(mPercentagePosition); 182 } 183 loadMenuTargetFeatures(OnInfoReady<List<AccessibilityTarget>> callback)184 void loadMenuTargetFeatures(OnInfoReady<List<AccessibilityTarget>> callback) { 185 callback.onReady(getTargets(mContext, SOFTWARE)); 186 } 187 loadMenuSizeType(OnInfoReady<Integer> callback)188 void loadMenuSizeType(OnInfoReady<Integer> callback) { 189 callback.onReady(getMenuSizeTypeFromSettings()); 190 } 191 loadMenuFadeEffectInfo(OnInfoReady<MenuFadeEffectInfo> callback)192 void loadMenuFadeEffectInfo(OnInfoReady<MenuFadeEffectInfo> callback) { 193 callback.onReady(getMenuFadeEffectInfo()); 194 } 195 getMenuFadeEffectInfo()196 private MenuFadeEffectInfo getMenuFadeEffectInfo() { 197 return new MenuFadeEffectInfo(isMenuFadeEffectEnabledFromSettings(), 198 getMenuOpacityFromSettings()); 199 } 200 updateMoveToTucked(boolean isMoveToTucked)201 void updateMoveToTucked(boolean isMoveToTucked) { 202 Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, 203 isMoveToTucked); 204 } 205 updateMenuSavingPosition(Position percentagePosition)206 void updateMenuSavingPosition(Position percentagePosition) { 207 mPercentagePosition = percentagePosition; 208 Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, 209 percentagePosition.toString()); 210 } 211 updateDockTooltipVisibility(boolean hasSeen)212 void updateDockTooltipVisibility(boolean hasSeen) { 213 Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, 214 hasSeen); 215 } 216 updateMigrationTooltipVisibility(boolean visible)217 void updateMigrationTooltipVisibility(boolean visible) { 218 mSecureSettings.putIntForUser( 219 ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, 220 visible ? MigrationPrompt.ENABLED : MigrationPrompt.DISABLED, 221 UserHandle.USER_CURRENT); 222 } 223 onTargetFeaturesChanged()224 private void onTargetFeaturesChanged() { 225 mSettingsContentsCallback.onTargetFeaturesChanged( 226 getTargets(mContext, SOFTWARE)); 227 } 228 getStartPosition()229 private Position getStartPosition() { 230 final String absolutePositionString = Prefs.getString(mContext, 231 Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null); 232 233 final float defaultPositionXPercent = 234 mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL 235 ? DEFAULT_MENU_POSITION_X_PERCENT_RTL 236 : DEFAULT_MENU_POSITION_X_PERCENT; 237 return TextUtils.isEmpty(absolutePositionString) 238 ? new Position(defaultPositionXPercent, DEFAULT_MENU_POSITION_Y_PERCENT) 239 : Position.fromString(absolutePositionString); 240 } 241 registerObserversAndCallbacks()242 void registerObserversAndCallbacks() { 243 mSecureSettings.registerContentObserverForUserSync( 244 mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS), 245 /* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver, 246 UserHandle.USER_CURRENT); 247 if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) { 248 mSecureSettings.registerContentObserverForUserSync( 249 mSecureSettings.getUriFor(ENABLED_ACCESSIBILITY_SERVICES), 250 /* notifyForDescendants */ false, 251 mMenuTargetFeaturesContentObserver, 252 UserHandle.USER_CURRENT); 253 } 254 mSecureSettings.registerContentObserverForUserSync( 255 mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE), 256 /* notifyForDescendants */ false, mMenuSizeContentObserver, 257 UserHandle.USER_CURRENT); 258 mSecureSettings.registerContentObserverForUserSync( 259 mSecureSettings.getUriFor(ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED), 260 /* notifyForDescendants */ false, mMenuFadeOutContentObserver, 261 UserHandle.USER_CURRENT); 262 mSecureSettings.registerContentObserverForUserSync( 263 mSecureSettings.getUriFor(ACCESSIBILITY_FLOATING_MENU_OPACITY), 264 /* notifyForDescendants */ false, mMenuFadeOutContentObserver, 265 UserHandle.USER_CURRENT); 266 mContext.registerComponentCallbacks(mComponentCallbacks); 267 268 if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) { 269 mAccessibilityManager.addAccessibilityServicesStateChangeListener( 270 mA11yServicesStateChangeListener); 271 } 272 } 273 unregisterObserversAndCallbacks()274 void unregisterObserversAndCallbacks() { 275 mContext.getContentResolver().unregisterContentObserver(mMenuTargetFeaturesContentObserver); 276 mContext.getContentResolver().unregisterContentObserver(mMenuSizeContentObserver); 277 mContext.getContentResolver().unregisterContentObserver(mMenuFadeOutContentObserver); 278 mContext.unregisterComponentCallbacks(mComponentCallbacks); 279 280 if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) { 281 mAccessibilityManager.removeAccessibilityServicesStateChangeListener( 282 mA11yServicesStateChangeListener); 283 } 284 } 285 286 interface OnSettingsContentsChanged { onTargetFeaturesChanged(List<AccessibilityTarget> newTargetFeatures)287 void onTargetFeaturesChanged(List<AccessibilityTarget> newTargetFeatures); 288 onSizeTypeChanged(int newSizeType)289 void onSizeTypeChanged(int newSizeType); 290 onFadeEffectInfoChanged(MenuFadeEffectInfo fadeEffectInfo)291 void onFadeEffectInfoChanged(MenuFadeEffectInfo fadeEffectInfo); 292 } 293 294 interface OnInfoReady<T> { onReady(T info)295 void onReady(T info); 296 } 297 getMenuSizeTypeFromSettings()298 private int getMenuSizeTypeFromSettings() { 299 return mSecureSettings.getIntForUser( 300 ACCESSIBILITY_FLOATING_MENU_SIZE, SMALL, UserHandle.USER_CURRENT); 301 } 302 isMenuFadeEffectEnabledFromSettings()303 private boolean isMenuFadeEffectEnabledFromSettings() { 304 return mSecureSettings.getIntForUser( 305 ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, 306 DEFAULT_FADE_EFFECT_IS_ENABLED, UserHandle.USER_CURRENT) == /* enabled */ 1; 307 } 308 getMenuOpacityFromSettings()309 private float getMenuOpacityFromSettings() { 310 return mSecureSettings.getFloatForUser( 311 ACCESSIBILITY_FLOATING_MENU_OPACITY, DEFAULT_OPACITY_VALUE, 312 UserHandle.USER_CURRENT); 313 } 314 } 315