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.accessibility; 18 19 import static android.content.Context.MODE_PRIVATE; 20 21 import static com.android.settings.accessibility.ToggleAutoclickPreferenceController.KEY_DELAY_MODE; 22 import static com.android.settings.accessibility.ToggleAutoclickPreferenceFragment.Quantity; 23 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.content.SharedPreferences; 27 import android.provider.Settings; 28 import android.view.accessibility.AccessibilityManager; 29 import android.widget.ImageView; 30 import android.widget.SeekBar; 31 import android.widget.TextView; 32 33 import androidx.annotation.VisibleForTesting; 34 import androidx.preference.PreferenceScreen; 35 36 import com.android.settings.R; 37 import com.android.settings.core.BasePreferenceController; 38 import com.android.settingslib.core.lifecycle.Lifecycle; 39 import com.android.settingslib.core.lifecycle.LifecycleObserver; 40 import com.android.settingslib.core.lifecycle.events.OnPause; 41 import com.android.settingslib.core.lifecycle.events.OnResume; 42 import com.android.settingslib.widget.LayoutPreference; 43 44 /** 45 * Controller class that controls accessibility autoclick seekbar settings. 46 */ 47 public class ToggleAutoclickCustomSeekbarController extends BasePreferenceController 48 implements LifecycleObserver, OnResume, OnPause, 49 SharedPreferences.OnSharedPreferenceChangeListener { 50 51 private static final String CONTROL_AUTOCLICK_DELAY_SECURE = 52 Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY; 53 54 @VisibleForTesting 55 static final String KEY_CUSTOM_DELAY_VALUE = "custom_delay_value"; 56 57 // Min allowed autoclick delay value. 58 static final int MIN_AUTOCLICK_DELAY_MS = 200; 59 60 // Max allowed autoclick delay value. 61 static final int MAX_AUTOCLICK_DELAY_MS = 1000; 62 63 // Allowed autoclick delay values are discrete. 64 // This is the difference between two allowed values. 65 @VisibleForTesting 66 static final int AUTOCLICK_DELAY_STEP = 100; 67 68 private final SharedPreferences mSharedPreferences; 69 private final ContentResolver mContentResolver; 70 private ImageView mShorter; 71 private ImageView mLonger; 72 private SeekBar mSeekBar; 73 private TextView mDelayLabel; 74 75 @VisibleForTesting 76 final SeekBar.OnSeekBarChangeListener mSeekBarChangeListener = 77 new SeekBar.OnSeekBarChangeListener() { 78 79 @Override 80 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 81 updateCustomDelayValue(seekBarProgressToDelay(progress)); 82 } 83 84 @Override 85 public void onStartTrackingTouch(SeekBar seekBar) { 86 // Nothing to do. 87 } 88 89 @Override 90 public void onStopTrackingTouch(SeekBar seekBar) { 91 // Nothing to do. 92 } 93 }; 94 ToggleAutoclickCustomSeekbarController(Context context, String preferenceKey)95 public ToggleAutoclickCustomSeekbarController(Context context, String preferenceKey) { 96 super(context, preferenceKey); 97 mSharedPreferences = context.getSharedPreferences(context.getPackageName(), MODE_PRIVATE); 98 mContentResolver = context.getContentResolver(); 99 } 100 ToggleAutoclickCustomSeekbarController(Context context, Lifecycle lifecycle, String preferenceKey)101 public ToggleAutoclickCustomSeekbarController(Context context, Lifecycle lifecycle, 102 String preferenceKey) { 103 this(context, preferenceKey); 104 105 if (lifecycle != null) { 106 lifecycle.addObserver(this); 107 } 108 } 109 110 @Override getAvailabilityStatus()111 public int getAvailabilityStatus() { 112 return AVAILABLE; 113 } 114 115 @Override onResume()116 public void onResume() { 117 if (mSharedPreferences != null) { 118 mSharedPreferences.registerOnSharedPreferenceChangeListener(this); 119 } 120 } 121 122 @Override onPause()123 public void onPause() { 124 if (mSharedPreferences != null) { 125 mSharedPreferences.unregisterOnSharedPreferenceChangeListener(this); 126 } 127 } 128 129 @Override displayPreference(PreferenceScreen screen)130 public void displayPreference(PreferenceScreen screen) { 131 super.displayPreference(screen); 132 final LayoutPreference preference = screen.findPreference(getPreferenceKey()); 133 134 if (isAvailable()) { 135 int delayMillis = getSharedPreferenceForDelayValue(); 136 // Initialize seek bar preference. Sets seek bar size to the number of possible delay 137 // values. 138 mSeekBar = preference.findViewById(R.id.autoclick_delay); 139 mSeekBar.setMax(delayToSeekBarProgress(MAX_AUTOCLICK_DELAY_MS)); 140 mSeekBar.setProgress(delayToSeekBarProgress(delayMillis)); 141 mSeekBar.setOnSeekBarChangeListener(mSeekBarChangeListener); 142 143 mDelayLabel = preference.findViewById(R.id.current_label); 144 mDelayLabel.setText(delayTimeToString(delayMillis)); 145 146 mShorter = preference.findViewById(R.id.shorter); 147 mShorter.setOnClickListener(v -> { 148 minusDelayByImageView(); 149 }); 150 151 mLonger = preference.findViewById(R.id.longer); 152 mLonger.setOnClickListener(v -> { 153 plusDelayByImageView(); 154 }); 155 } 156 } 157 158 @Override onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)159 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 160 if (KEY_DELAY_MODE.equals(key)) { 161 final int delayMillis = getSharedPreferenceForDelayValue(); 162 updateCustomDelayValue(delayMillis); 163 } 164 } 165 166 /** Converts seek bar preference progress value to autoclick delay associated with it. */ seekBarProgressToDelay(int progress)167 private int seekBarProgressToDelay(int progress) { 168 return progress * AUTOCLICK_DELAY_STEP + MIN_AUTOCLICK_DELAY_MS; 169 } 170 171 /** 172 * Converts autoclick delay value to seek bar preference progress values that represents said 173 * delay. 174 */ delayToSeekBarProgress(int delayMillis)175 private int delayToSeekBarProgress(int delayMillis) { 176 return (delayMillis - MIN_AUTOCLICK_DELAY_MS) / AUTOCLICK_DELAY_STEP; 177 } 178 getSharedPreferenceForDelayValue()179 private int getSharedPreferenceForDelayValue() { 180 final int delayMillis = Settings.Secure.getInt(mContentResolver, 181 Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY, 182 AccessibilityManager.AUTOCLICK_DELAY_DEFAULT); 183 184 return mSharedPreferences.getInt(KEY_CUSTOM_DELAY_VALUE, delayMillis); 185 } 186 putSecureInt(String name, int value)187 private void putSecureInt(String name, int value) { 188 Settings.Secure.putInt(mContentResolver, name, value); 189 } 190 updateCustomDelayValue(int delayMillis)191 private void updateCustomDelayValue(int delayMillis) { 192 putSecureInt(CONTROL_AUTOCLICK_DELAY_SECURE, delayMillis); 193 mSharedPreferences.edit().putInt(KEY_CUSTOM_DELAY_VALUE, delayMillis).apply(); 194 mSeekBar.setProgress(delayToSeekBarProgress(delayMillis)); 195 mDelayLabel.setText(delayTimeToString(delayMillis)); 196 } 197 minusDelayByImageView()198 private void minusDelayByImageView() { 199 final int delayMillis = getSharedPreferenceForDelayValue(); 200 if (delayMillis > MIN_AUTOCLICK_DELAY_MS) { 201 updateCustomDelayValue(delayMillis - AUTOCLICK_DELAY_STEP); 202 } 203 } 204 plusDelayByImageView()205 private void plusDelayByImageView() { 206 final int delayMillis = getSharedPreferenceForDelayValue(); 207 if (delayMillis < MAX_AUTOCLICK_DELAY_MS) { 208 updateCustomDelayValue(delayMillis + AUTOCLICK_DELAY_STEP); 209 } 210 } 211 delayTimeToString(int delayMillis)212 private CharSequence delayTimeToString(int delayMillis) { 213 final int quantity = (delayMillis == 1000) ? Quantity.ONE : Quantity.FEW; 214 final float delaySecond = (float) delayMillis / 1000; 215 // Only show integer when delay time is 1. 216 final String decimalFormat = (delaySecond == 1) ? "%.0f" : "%.1f"; 217 218 return mContext.getResources().getQuantityString( 219 R.plurals.accessibilty_autoclick_delay_unit_second, 220 quantity, String.format(decimalFormat, delaySecond)); 221 } 222 } 223