Merge "Add quick access wallet tile." into sc-dev

This commit is contained in:
Silin Huang
2021-03-18 06:50:52 +00:00
committed by Android (Google) Code Review
9 changed files with 382 additions and 2 deletions

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20,4L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,
-0.89 2,-2L22,6c0,-1.11 -0.89,-2 -2,-2zM20,18L4,18v-6h16v6zM20,8L4,8L4,6h16v2z"/>
</vector>

View File

@@ -107,7 +107,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls,alarm
wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle,controls,alarm,wallet
</string>
<!-- The tiles to display in QuickSettings -->

View File

@@ -38,6 +38,8 @@
<!-- People Tile flag -->
<bool name="flag_conversations">false</bool>
<bool name="flag_wallet">false</bool>
<!-- The new animations to/from lockscreen and AOD! -->
<bool name="flag_lockscreen_animations">false</bool>

View File

@@ -1621,6 +1621,12 @@
<!-- Name of the alarm status bar icon. -->
<string name="status_bar_alarm">Alarm</string>
<!-- Wallet strings -->
<!-- Wallet empty state, title [CHAR LIMIT=32] -->
<string name="wallet_title">Wallet</string>
<!-- Secondary label of the quick access wallet tile. [CHAR LIMIT=32] -->
<string name="wallet_secondary_label">Ready</string>
<!-- Name of the work status bar icon. -->
<string name="status_bar_work">Work profile</string>

View File

@@ -31,6 +31,7 @@ import android.os.HandlerThread;
import android.os.Looper;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.view.Choreographer;
import android.view.IWindowManager;
import android.view.LayoutInflater;
@@ -360,4 +361,11 @@ public class DependencyProvider {
public ModeSwitchesController providesModeSwitchesController(Context context) {
return new ModeSwitchesController(context);
}
/** */
@Provides
@SysUISingleton
public QuickAccessWalletClient provideQuickAccessWalletClient(Context context) {
return QuickAccessWalletClient.create(context);
}
}

View File

@@ -47,6 +47,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.QuickAccessWalletTile;
import com.android.systemui.qs.tiles.ReduceBrightColorsTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.qs.tiles.ScreenRecordTile;
@@ -93,6 +94,7 @@ public class QSFactoryImpl implements QSFactory {
private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider;
private final Provider<DeviceControlsTile> mDeviceControlsTileProvider;
private final Provider<AlarmTile> mAlarmTileProvider;
private final Provider<QuickAccessWalletTile> mQuickAccessWalletTileProvider;
private final Lazy<QSHost> mQsHostLazy;
private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
@@ -129,7 +131,8 @@ public class QSFactoryImpl implements QSFactory {
Provider<CameraToggleTile> cameraToggleTileProvider,
Provider<MicrophoneToggleTile> microphoneToggleTileProvider,
Provider<DeviceControlsTile> deviceControlsTileProvider,
Provider<AlarmTile> alarmTileProvider) {
Provider<AlarmTile> alarmTileProvider,
Provider<QuickAccessWalletTile> quickAccessWalletTileProvider) {
mQsHostLazy = qsHostLazy;
mCustomTileBuilderProvider = customTileBuilderProvider;
@@ -161,6 +164,7 @@ public class QSFactoryImpl implements QSFactory {
mMicrophoneToggleTileProvider = microphoneToggleTileProvider;
mDeviceControlsTileProvider = deviceControlsTileProvider;
mAlarmTileProvider = alarmTileProvider;
mQuickAccessWalletTileProvider = quickAccessWalletTileProvider;
}
public QSTile createTile(String tileSpec) {
@@ -224,6 +228,8 @@ public class QSFactoryImpl implements QSFactory {
return mDeviceControlsTileProvider.get();
case "alarm":
return mAlarmTileProvider.get();
case "wallet":
return mQuickAccessWalletTileProvider.get();
}
// Custom tiles

View File

@@ -0,0 +1,136 @@
/*
* 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 android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.service.quicksettings.Tile;
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;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.settings.SecureSettings;
import javax.inject.Inject;
/** Quick settings tile: Quick access wallet **/
public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
private static final String FEATURE_CHROME_OS = "org.chromium.arc";
private final CharSequence mLabel = mContext.getString(R.string.wallet_title);
// TODO(b/180959290): Re-create the QAW Client when the default NFC payment app changes.
private final QuickAccessWalletClient mQuickAccessWalletClient;
private final KeyguardStateController mKeyguardStateController;
private final PackageManager mPackageManager;
private final SecureSettings mSecureSettings;
private final FeatureFlags mFeatureFlags;
@Inject
public QuickAccessWalletTile(
QSHost host,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
MetricsLogger metricsLogger,
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
QuickAccessWalletClient quickAccessWalletClient,
KeyguardStateController keyguardStateController,
PackageManager packageManager,
SecureSettings secureSettings,
FeatureFlags featureFlags) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mQuickAccessWalletClient = quickAccessWalletClient;
mKeyguardStateController = keyguardStateController;
mPackageManager = packageManager;
mSecureSettings = secureSettings;
mFeatureFlags = featureFlags;
}
@Override
public State newTileState() {
State state = new State();
state.handlesLongClick = false;
return state;
}
@Override
protected void handleClick() {
mActivityStarter.postStartActivityDismissingKeyguard(
mQuickAccessWalletClient.createWalletIntent(), /* delay= */ 0);
}
@Override
protected void handleUpdateState(State state, Object arg) {
CharSequence qawLabel = mQuickAccessWalletClient.getServiceLabel();
state.label = qawLabel == null ? mLabel : qawLabel;
state.contentDescription = state.label;
state.icon = ResourceIcon.get(R.drawable.ic_qs_wallet);
boolean isDeviceLocked = !mKeyguardStateController.isUnlocked();
if (mQuickAccessWalletClient.isWalletFeatureAvailable()) {
state.state = isDeviceLocked ? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE;
state.secondaryLabel = isDeviceLocked
? null
: mContext.getString(R.string.wallet_secondary_label);
state.stateDescription = state.secondaryLabel;
} else {
state.state = Tile.STATE_UNAVAILABLE;
}
}
@Override
public int getMetricsCategory() {
return 0;
}
@Override
public boolean isAvailable() {
return mFeatureFlags.isQuickAccessWalletEnabled()
&& mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
&& !mPackageManager.hasSystemFeature(FEATURE_CHROME_OS)
&& mSecureSettings.getString(NFC_PAYMENT_DEFAULT_COMPONENT) != null;
}
@Override
public Intent getLongClickIntent() {
return null;
}
@Override
public CharSequence getTileLabel() {
CharSequence qawLabel = mQuickAccessWalletClient.getServiceLabel();
return qawLabel == null ? mLabel : qawLabel;
}
}

View File

@@ -83,6 +83,10 @@ public class FeatureFlags {
return mFlagReader.isEnabled(R.bool.flag_monet);
}
public boolean isQuickAccessWalletEnabled() {
return mFlagReader.isEnabled(R.bool.flag_wallet);
}
public boolean isNavigationBarOverlayEnabled() {
return mFlagReader.isEnabled(R.bool.flag_navigation_bar_overlay);
}

View File

@@ -0,0 +1,207 @@
/*
* 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 android.content.pm.PackageManager.FEATURE_NFC_HOST_CARD_EMULATION;
import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.KeyguardStateController;
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 QuickAccessWalletTileTest extends SysuiTestCase {
@Mock
private QSTileHost mHost;
@Mock
private MetricsLogger mMetricsLogger;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
private ActivityStarter mActivityStarter;
@Mock
private QSLogger mQSLogger;
private UiEventLogger mUiEventLogger = new UiEventLoggerFake();
@Mock
private QuickAccessWalletClient mQuickAccessWalletClient;
@Mock
private KeyguardStateController mKeyguardStateController;
@Mock
private PackageManager mPackageManager;
@Mock
private SecureSettings mSecureSettings;
@Mock
private FeatureFlags mFeatureFlags;
private TestableLooper mTestableLooper;
private QuickAccessWalletTile mTile;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
when(mHost.getContext()).thenReturn(mContext);
when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
when(mFeatureFlags.isQuickAccessWalletEnabled()).thenReturn(true);
mTile = new QuickAccessWalletTile(
mHost,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
mMetricsLogger,
mStatusBarStateController,
mActivityStarter,
mQSLogger,
mQuickAccessWalletClient,
mKeyguardStateController,
mPackageManager,
mSecureSettings,
mFeatureFlags);
}
@Test
public void testNewTile() {
assertFalse(mTile.newTileState().handlesLongClick);
}
@Test
public void testIsAvailable_featureFlagIsOff() {
when(mFeatureFlags.isQuickAccessWalletEnabled()).thenReturn(false);
assertFalse(mTile.isAvailable());
}
@Test
public void testIsAvailable_qawServiceNotAvailable() {
when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false);
assertFalse(mTile.isAvailable());
}
@Test
public void testIsAvailable_qawServiceAvailable() {
when(mPackageManager.hasSystemFeature(FEATURE_NFC_HOST_CARD_EMULATION)).thenReturn(true);
when(mPackageManager.hasSystemFeature("org.chromium.arc")).thenReturn(false);
when(mSecureSettings.getString(NFC_PAYMENT_DEFAULT_COMPONENT)).thenReturn("Component");
assertTrue(mTile.isAvailable());
}
@Test
public void testHandleClick_openGPay() {
Intent intent = new Intent("WalletIntent");
when(mQuickAccessWalletClient.createWalletIntent()).thenReturn(intent);
mTile.handleClick();
verify(mActivityStarter, times(1))
.postStartActivityDismissingKeyguard(eq(intent), anyInt());
}
@Test
public void testHandleUpdateState_updateLabelAndIcon() {
QSTile.Icon icon = QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_wallet);
QSTile.State state = new QSTile.State();
when(mQuickAccessWalletClient.getServiceLabel()).thenReturn("QuickAccessWallet");
mTile.handleUpdateState(state, new Object());
assertEquals("QuickAccessWallet", state.label.toString());
assertTrue(state.label.toString().contentEquals(state.contentDescription));
assertEquals(icon, state.icon);
}
@Test
public void testHandleUpdateState_deviceLocked_tileInactive() {
QSTile.State state = new QSTile.State();
when(mKeyguardStateController.isUnlocked()).thenReturn(false);
when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true);
mTile.handleUpdateState(state, new Object());
assertEquals(Tile.STATE_INACTIVE, state.state);
assertNull(state.stateDescription);
}
@Test
public void testHandleUpdateState_deviceLocked_tileActive() {
QSTile.State state = new QSTile.State();
when(mKeyguardStateController.isUnlocked()).thenReturn(true);
when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true);
mTile.handleUpdateState(state, new Object());
assertEquals(Tile.STATE_ACTIVE, state.state);
assertTrue(state.secondaryLabel.toString().contentEquals(state.stateDescription));
assertEquals(
getContext().getString(R.string.wallet_secondary_label),
state.secondaryLabel.toString());
}
@Test
public void testHandleUpdateState_qawFeatureUnavailable_tileUnavailable() {
QSTile.State state = new QSTile.State();
when(mKeyguardStateController.isUnlocked()).thenReturn(true);
when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(false);
mTile.handleUpdateState(state, new Object());
assertEquals(Tile.STATE_UNAVAILABLE, state.state);
}
}