diff options
9 files changed, 382 insertions, 2 deletions
diff --git a/packages/SystemUI/res/drawable/ic_qs_wallet.xml b/packages/SystemUI/res/drawable/ic_qs_wallet.xml new file mode 100644 index 000000000000..e146eabecfd3 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_wallet.xml @@ -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> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index b0c5239bb4b8..af6df32a02b0 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -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 --> diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index 8cd5757247e2..2163806666d9 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -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> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 87fa4f831e37..78180a7a80a8 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -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> diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index 123ccee80179..8e344d2c2df7 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java @@ -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); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 29b9e64d1659..ba349c6273d9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java new file mode 100644 index 000000000000..60c5d1cafde9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -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; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java index c8e0d60ce304..c3de81c3c66a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java @@ -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); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java new file mode 100644 index 000000000000..33166ccadc27 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -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); + } +} |