Merge "(1/n) Implement One-handed mode Quick Settings tile"

This commit is contained in:
Jason Chang
2021-11-17 23:38:02 +00:00
committed by Android (Google) Code Review
11 changed files with 321 additions and 3 deletions

View File

@@ -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.

View 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>

View File

@@ -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" />

View File

@@ -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.
*/

View File

@@ -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 -->

View File

@@ -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] -->

View File

@@ -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>

View File

@@ -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

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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);
}
}