Merge "(1/n) Implement One-handed mode Quick Settings tile"
This commit is contained in:
@@ -1563,6 +1563,15 @@ public class Intent implements Parcelable, Cloneable {
|
||||
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
|
||||
public static final String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY";
|
||||
|
||||
/**
|
||||
* Activity Action: Show one-handed mode settings to the user.
|
||||
* <p>Input: Nothing.
|
||||
* <p>Output: Nothing.
|
||||
* @hide
|
||||
*/
|
||||
public static final String ACTION_ONE_HANDED_SETTINGS =
|
||||
"android.intent.action.ONE_HANDED_SETTINGS";
|
||||
|
||||
/**
|
||||
* Activity Action: Setup wizard action provided for OTA provisioning to determine if it needs
|
||||
* to run.
|
||||
|
||||
26
core/res/res/drawable/ic_qs_one_handed_mode.xml
Normal file
26
core/res/res/drawable/ic_qs_one_handed_mode.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2021 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="18dp"
|
||||
android:height="18dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path android:fillColor="@android:color/white"
|
||||
android:pathData="M4.64169 3C3.88567 3 3.27271 3.61296 3.27271 4.36898V18.4011C3.27271 19.154 3.88875 19.7701 4.64169 19.7701H12.5339C12.5339 19.7701 12.5425 18.0588 11.2324 18.0588H6.01067C5.44597 18.0588 4.98393 17.5968 4.98393 17.0321V5.73797C4.98393 5.17326 5.44597 4.71123 6.01067 4.71123H15.9358C16.5005 4.71123 16.9625 5.17326 16.9625 5.73797V9.84492C16.9625 10.9651 16.4899 12.5952 15.5936 13.2674L9.77538 9.16043C8.58505 10.425 8.88177 11.705 10.1176 12.9251L13.1978 16.0053C13.1978 16.0053 13.3231 17.4572 13.5401 18.0588C14.2034 19.8984 16.2781 20.7968 16.2781 20.7968H19.231H19.2967C20.0835 20.7968 20.7273 20.153 20.7273 19.3662V12.2543L18.9441 4.06781C18.7662 3.23718 18.4068 3 17.8729 3H4.64169Z"/>
|
||||
</vector>
|
||||
@@ -1438,6 +1438,7 @@
|
||||
<java-symbol type="drawable" name="ic_qs_flashlight" />
|
||||
<java-symbol type="drawable" name="ic_qs_auto_rotate" />
|
||||
<java-symbol type="drawable" name="ic_qs_dnd" />
|
||||
<java-symbol type="drawable" name="ic_qs_one_handed_mode" />
|
||||
|
||||
<java-symbol type="drawable" name="sim_light_blue" />
|
||||
<java-symbol type="drawable" name="sim_light_green" />
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.wm.shell.onehanded;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.os.SystemProperties;
|
||||
|
||||
import com.android.wm.shell.common.annotations.ExternalThread;
|
||||
|
||||
@@ -26,6 +27,9 @@ import com.android.wm.shell.common.annotations.ExternalThread;
|
||||
@ExternalThread
|
||||
public interface OneHanded {
|
||||
|
||||
boolean sIsSupportOneHandedMode = SystemProperties.getBoolean(
|
||||
OneHandedController.SUPPORT_ONE_HANDED_MODE, false);
|
||||
|
||||
/**
|
||||
* Returns a binder that can be passed to an external process to manipulate OneHanded.
|
||||
*/
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
|
||||
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
|
||||
<string name="quick_settings_tiles_stock" translatable="false">
|
||||
internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner
|
||||
internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded
|
||||
</string>
|
||||
|
||||
<!-- The tiles to display in QuickSettings -->
|
||||
|
||||
@@ -722,6 +722,9 @@
|
||||
<!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
|
||||
<string name="quick_settings_screen_record_stop">Stop</string>
|
||||
|
||||
<!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] -->
|
||||
<string name="quick_settings_onehanded_label">One-handed mode</string>
|
||||
|
||||
<!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] -->
|
||||
<string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string>
|
||||
<!--- Title of dialog triggered if the camera is disabled but an app tried to access it. [CHAR LIMIT=150] -->
|
||||
|
||||
@@ -291,4 +291,11 @@
|
||||
<item>Off</item>
|
||||
<item>On</item>
|
||||
</string-array>
|
||||
|
||||
<!-- State names for One-handed mode tile: unavailable, off, on [CHAR LIMIT=32] -->
|
||||
<string-array name="tile_states_onehanded">
|
||||
<item>Unavailable</item>
|
||||
<item>Off</item>
|
||||
<item>On</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -43,6 +43,7 @@ import com.android.systemui.qs.tiles.LocationTile;
|
||||
import com.android.systemui.qs.tiles.MicrophoneToggleTile;
|
||||
import com.android.systemui.qs.tiles.NfcTile;
|
||||
import com.android.systemui.qs.tiles.NightDisplayTile;
|
||||
import com.android.systemui.qs.tiles.OneHandedModeTile;
|
||||
import com.android.systemui.qs.tiles.QRCodeScannerTile;
|
||||
import com.android.systemui.qs.tiles.QuickAccessWalletTile;
|
||||
import com.android.systemui.qs.tiles.ReduceBrightColorsTile;
|
||||
@@ -92,6 +93,7 @@ public class QSFactoryImpl implements QSFactory {
|
||||
private final Provider<AlarmTile> mAlarmTileProvider;
|
||||
private final Provider<QuickAccessWalletTile> mQuickAccessWalletTileProvider;
|
||||
private final Provider<QRCodeScannerTile> mQRCodeScannerTileProvider;
|
||||
private final Provider<OneHandedModeTile> mOneHandedModeTileProvider;
|
||||
|
||||
private final Lazy<QSHost> mQsHostLazy;
|
||||
private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
|
||||
@@ -127,7 +129,8 @@ public class QSFactoryImpl implements QSFactory {
|
||||
Provider<DeviceControlsTile> deviceControlsTileProvider,
|
||||
Provider<AlarmTile> alarmTileProvider,
|
||||
Provider<QuickAccessWalletTile> quickAccessWalletTileProvider,
|
||||
Provider<QRCodeScannerTile> qrCodeScannerTileProvider) {
|
||||
Provider<QRCodeScannerTile> qrCodeScannerTileProvider,
|
||||
Provider<OneHandedModeTile> oneHandedModeTileProvider) {
|
||||
mQsHostLazy = qsHostLazy;
|
||||
mCustomTileBuilderProvider = customTileBuilderProvider;
|
||||
|
||||
@@ -159,6 +162,7 @@ public class QSFactoryImpl implements QSFactory {
|
||||
mAlarmTileProvider = alarmTileProvider;
|
||||
mQuickAccessWalletTileProvider = quickAccessWalletTileProvider;
|
||||
mQRCodeScannerTileProvider = qrCodeScannerTileProvider;
|
||||
mOneHandedModeTileProvider = oneHandedModeTileProvider;
|
||||
}
|
||||
|
||||
public QSTile createTile(String tileSpec) {
|
||||
@@ -227,6 +231,8 @@ public class QSFactoryImpl implements QSFactory {
|
||||
return mQuickAccessWalletTileProvider.get();
|
||||
case "qr_code_scanner":
|
||||
return mQRCodeScannerTileProvider.get();
|
||||
case "onehanded":
|
||||
return mOneHandedModeTileProvider.get();
|
||||
}
|
||||
|
||||
// Custom tiles
|
||||
|
||||
@@ -648,7 +648,8 @@ internal object SubtitleArrayMapping {
|
||||
"controls" to R.array.tile_states_controls,
|
||||
"wallet" to R.array.tile_states_wallet,
|
||||
"qr_code_scanner" to R.array.tile_states_qr_code_scanner,
|
||||
"alarm" to R.array.tile_states_alarm
|
||||
"alarm" to R.array.tile_states_alarm,
|
||||
"onehanded" to R.array.tile_states_onehanded
|
||||
)
|
||||
|
||||
fun getSubtitleId(spec: String?): Int {
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.qs.tiles;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
import android.service.quicksettings.Tile;
|
||||
import android.view.View;
|
||||
import android.widget.Switch;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.dagger.qualifiers.Background;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.plugins.qs.QSTile.BooleanState;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.qs.QSHost;
|
||||
import com.android.systemui.qs.SecureSetting;
|
||||
import com.android.systemui.qs.logging.QSLogger;
|
||||
import com.android.systemui.qs.tileimpl.QSTileImpl;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
import com.android.systemui.util.settings.SecureSettings;
|
||||
import com.android.wm.shell.onehanded.OneHanded;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Quick settings tile: One-handed mode **/
|
||||
public class OneHandedModeTile extends QSTileImpl<BooleanState> {
|
||||
private final Icon mIcon = ResourceIcon.get(
|
||||
com.android.internal.R.drawable.ic_qs_one_handed_mode);
|
||||
private final SecureSetting mSetting;
|
||||
|
||||
@Inject
|
||||
public OneHandedModeTile(
|
||||
QSHost host,
|
||||
@Background Looper backgroundLooper,
|
||||
@Main Handler mainHandler,
|
||||
FalsingManager falsingManager,
|
||||
MetricsLogger metricsLogger,
|
||||
StatusBarStateController statusBarStateController,
|
||||
ActivityStarter activityStarter,
|
||||
QSLogger qsLogger,
|
||||
UserTracker userTracker,
|
||||
SecureSettings secureSettings) {
|
||||
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
|
||||
statusBarStateController, activityStarter, qsLogger);
|
||||
mSetting = new SecureSetting(secureSettings, mHandler,
|
||||
Settings.Secure.ONE_HANDED_MODE_ENABLED, userTracker.getUserId()) {
|
||||
@Override
|
||||
protected void handleValueChanged(int value, boolean observedChange) {
|
||||
// mHandler is the background handler so calling this is OK
|
||||
handleRefreshState(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return isSupportOneHandedMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleDestroy() {
|
||||
super.handleDestroy();
|
||||
mSetting.setListening(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BooleanState newTileState() {
|
||||
return new BooleanState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSetListening(boolean listening) {
|
||||
super.handleSetListening(listening);
|
||||
mSetting.setListening(listening);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleUserSwitch(int newUserId) {
|
||||
mSetting.setUserId(newUserId);
|
||||
handleRefreshState(mSetting.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getLongClickIntent() {
|
||||
return new Intent(Intent.ACTION_ONE_HANDED_SETTINGS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleClick(@Nullable View view) {
|
||||
mSetting.setValue(mState.value ? 0 : 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTileLabel() {
|
||||
return mContext.getString(R.string.quick_settings_onehanded_label);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleUpdateState(BooleanState state, Object arg) {
|
||||
final int value = arg instanceof Integer ? (Integer) arg : mSetting.getValue();
|
||||
final boolean enabled = value != 0;
|
||||
state.value = enabled;
|
||||
state.label = mContext.getString(R.string.quick_settings_onehanded_label);
|
||||
state.icon = mIcon;
|
||||
if (state.slash == null) {
|
||||
state.slash = new SlashState();
|
||||
}
|
||||
state.slash.isSlashed = !state.value;
|
||||
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
|
||||
state.contentDescription = state.label;
|
||||
state.expandedAccessibilityClassName = Switch.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
// MetricsProto/MetricsEvent is deprecated, and QSTileImpl has code to log events to Tron,
|
||||
// as well as UiEventLogger, so just simply return 0 here.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public boolean isSupportOneHandedMode() {
|
||||
return OneHanded.sIsSupportOneHandedMode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.qs.tiles;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.classifier.FalsingManagerFake;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.qs.QSTileHost;
|
||||
import com.android.systemui.qs.logging.QSLogger;
|
||||
import com.android.systemui.settings.UserTracker;
|
||||
import com.android.systemui.util.settings.SecureSettings;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||
@SmallTest
|
||||
public class OneHandedModeTileTest extends SysuiTestCase {
|
||||
|
||||
private final String mOneHandedTitle = "One-handed mode";
|
||||
|
||||
@Mock
|
||||
private ActivityStarter mActivityStarter;
|
||||
@Mock
|
||||
private QSTileHost mHost;
|
||||
@Mock
|
||||
private MetricsLogger mMetricsLogger;
|
||||
@Mock
|
||||
private StatusBarStateController mStatusBarStateController;
|
||||
@Mock
|
||||
private QSLogger mQSLogger;
|
||||
@Mock
|
||||
private UserTracker mUserTracker;
|
||||
@Mock
|
||||
private SecureSettings mSecureSettings;
|
||||
|
||||
private TestableLooper mTestableLooper;
|
||||
private OneHandedModeTile mTile;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mTestableLooper = TestableLooper.get(this);
|
||||
|
||||
when(mHost.getContext()).thenReturn(mContext);
|
||||
|
||||
mTile = spy(new OneHandedModeTile(
|
||||
mHost,
|
||||
mTestableLooper.getLooper(),
|
||||
new Handler(mTestableLooper.getLooper()),
|
||||
new FalsingManagerFake(),
|
||||
mMetricsLogger,
|
||||
mStatusBarStateController,
|
||||
mActivityStarter,
|
||||
mQSLogger,
|
||||
mUserTracker,
|
||||
mSecureSettings));
|
||||
|
||||
mTestableLooper.processAllMessages();
|
||||
mTile.initialize();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsAvailable_unsupportOneHandedProperty_shouldReturnsFalse() {
|
||||
when(mTile.isSupportOneHandedMode()).thenReturn(false);
|
||||
|
||||
assertThat(mTile.isAvailable()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsAvailable_supportOneHandedProperty_shouldReturnsTrue() {
|
||||
when(mTile.isSupportOneHandedMode()).thenReturn(true);
|
||||
|
||||
assertThat(mTile.isAvailable()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTileLabel_shouldReturnOneHandedModeTitle() {
|
||||
assertThat(mTile.getTileLabel()).isEqualTo(mOneHandedTitle);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user