From 21d16aecd77919f1d3f2588230016ffbfa415bba Mon Sep 17 00:00:00 2001 From: Scottie Biddle Date: Tue, 18 Jan 2022 21:33:38 +0000 Subject: Add new config and API for QuickAccessWallet features. Adds a new configuration attribute for use in QuickAccessWalletService: * useTargetActivityForQuickAccess - this is a boolean, false by default. When true, the system will use the component specified by the android:targetActivity attribute instead of the SysUi's WalletActivity. Adds a new API method to QuickAccessWalletService: the method returns a PendingIntent that can be used in place of the targetActivity specified in the service's manifest entry. The method is not abstract; by default it returns null so that existing services will not have to make any changes to target android T. To make use of this feature, define a new method in QuickAccessWalletClient: useTargetActivityForQuickAccess() which provides the configuration for use by QuickAccessWalletController. Updates QuickAccessWalletController to define a new method: startQuickAccessUiIntent(), which reads the configuration as well as whether the service is providing any cards, and provides a centralized place to launch the correct Quick Access intent. The logic there is: 1. If a PendingIntent is provided from the service, use that. 2. If not, check for an active activity from the metadata in XML. 3. If that isn't available OR useTargetActivityForQuickAccess is false and the system is displaying cards, use WalletActivity provided by SystemUi. Addes tests for QuickAccessWalletController. CTS Test for QuickAccessWalletClient has new tests in a separate change in the same topic. In the course of development, discovered that unit tests for QuickAccessWalletController has been failing for as long as a year. Will fix that and update in a follow-up. Test: atest QuickAccessWalletControllerTest Test: atest QuickAccessWalletClientTest Bug: 215725263 Change-Id: Ifa3427d5e6f6b02e4afffa40453d1cb0f4f8ed67 --- core/api/current.txt | 2 + core/api/test-current.txt | 6 ++ .../IQuickAccessWalletService.aidl | 4 +- .../IQuickAccessWalletServiceCallbacks.aidl | 3 + .../quickaccesswallet/QuickAccessWalletClient.java | 27 +++++++ .../QuickAccessWalletClientImpl.java | 37 ++++++++-- .../QuickAccessWalletService.java | 23 ++++++ .../QuickAccessWalletServiceInfo.java | 16 ++++- core/res/res/values/attrs.xml | 3 +- core/res/res/values/public.xml | 1 + .../controller/QuickAccessWalletController.java | 84 +++++++++++++++++++++- .../QuickAccessWalletControllerTest.java | 77 +++++++++++++++++++- 12 files changed, 270 insertions(+), 13 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index 058861a214b9..cbc9f48616d2 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1642,6 +1642,7 @@ package android { field public static final int useEmbeddedDex = 16844190; // 0x101059e field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310 field public static final int useLevel = 16843167; // 0x101019f + field public static final int useTargetActivityForQuickAccess; field public static final int userVisible = 16843409; // 0x1010291 field public static final int usesCleartextTraffic = 16844012; // 0x10104ec field public static final int usesPermissionFlags = 16844356; // 0x1010644 @@ -38990,6 +38991,7 @@ package android.service.quickaccesswallet { public abstract class QuickAccessWalletService extends android.app.Service { ctor public QuickAccessWalletService(); + method @Nullable public android.app.PendingIntent getTargetActivityPendingIntent(); method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onWalletCardSelected(@NonNull android.service.quickaccesswallet.SelectWalletCardRequest); method public abstract void onWalletCardsRequested(@NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.GetWalletCardsCallback); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 4132c64a44c9..5bd6ca827e29 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2357,12 +2357,14 @@ package android.service.quickaccesswallet { method public void disconnect(); method public void getWalletCards(@NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback); method public void getWalletCards(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.GetWalletCardsRequest, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.OnWalletCardsRetrievedCallback); + method public void getWalletPendingIntent(@NonNull java.util.concurrent.Executor, @NonNull android.service.quickaccesswallet.QuickAccessWalletClient.WalletPendingIntentCallback); method public boolean isWalletFeatureAvailable(); method public boolean isWalletFeatureAvailableWhenDeviceLocked(); method public boolean isWalletServiceAvailable(); method public void notifyWalletDismissed(); method public void removeWalletServiceEventListener(@NonNull android.service.quickaccesswallet.QuickAccessWalletClient.WalletServiceEventListener); method public void selectWalletCard(@NonNull android.service.quickaccesswallet.SelectWalletCardRequest); + method public boolean useTargetActivityForQuickAccess(); } public static interface QuickAccessWalletClient.OnWalletCardsRetrievedCallback { @@ -2370,6 +2372,10 @@ package android.service.quickaccesswallet { method public void onWalletCardsRetrieved(@NonNull android.service.quickaccesswallet.GetWalletCardsResponse); } + public static interface QuickAccessWalletClient.WalletPendingIntentCallback { + method public void onWalletPendingIntentRetrieved(@Nullable android.app.PendingIntent); + } + public static interface QuickAccessWalletClient.WalletServiceEventListener { method public void onWalletServiceEvent(@NonNull android.service.quickaccesswallet.WalletServiceEvent); } diff --git a/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl b/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl index ee70be442d16..0dca78d890a5 100644 --- a/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl +++ b/core/java/android/service/quickaccesswallet/IQuickAccessWalletService.aidl @@ -41,4 +41,6 @@ interface IQuickAccessWalletService { in IQuickAccessWalletServiceCallbacks callback); // Unregister an event listener oneway void unregisterWalletServiceEventListener(in WalletServiceEventListenerRequest request); -} \ No newline at end of file + // Request to get a PendingIntent to launch an activity from which the user can manage their cards. + oneway void onTargetActivityIntentRequested(in IQuickAccessWalletServiceCallbacks callbacks); + } \ No newline at end of file diff --git a/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl b/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl index f37b930aabce..1b69ca12da3a 100644 --- a/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl +++ b/core/java/android/service/quickaccesswallet/IQuickAccessWalletServiceCallbacks.aidl @@ -16,6 +16,7 @@ package android.service.quickaccesswallet; +import android.app.PendingIntent; import android.service.quickaccesswallet.GetWalletCardsError; import android.service.quickaccesswallet.GetWalletCardsResponse; import android.service.quickaccesswallet.WalletServiceEvent; @@ -34,4 +35,6 @@ interface IQuickAccessWalletServiceCallbacks { // Called in response to registerWalletServiceEventListener. May be called multiple times as // long as the event listener is registered. oneway void onWalletServiceEvent(in WalletServiceEvent event); + // Called in response to onTargetActivityIntentRequested. May only be called once per request. + oneway void onTargetActivityPendingIntentReceived(in PendingIntent pendingIntent); } \ No newline at end of file diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java index f69c89d45312..38659e12b1ce 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClient.java @@ -20,6 +20,7 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Drawable; @@ -152,6 +153,21 @@ public interface QuickAccessWalletClient extends Closeable { */ void disconnect(); + /** + * The QuickAccessWalletService may provide a {@link PendingIntent} to start the activity that + * hosts the Wallet view. This is typically the home screen of the Wallet application. If this + * method returns null, the value returned by getWalletIntent() will be used instead. + */ + void getWalletPendingIntent(@NonNull @CallbackExecutor Executor executor, + @NonNull WalletPendingIntentCallback walletPendingIntentCallback); + + /** + * Callback for getWalletPendingIntent. + */ + interface WalletPendingIntentCallback { + void onWalletPendingIntentRetrieved(@Nullable PendingIntent walletPendingIntent); + } + /** * The manifest entry for the QuickAccessWalletService may also publish information about the * activity that hosts the Wallet view. This is typically the home screen of the Wallet @@ -212,4 +228,15 @@ public interface QuickAccessWalletClient extends Closeable { */ @Nullable CharSequence getShortcutLongLabel(); + + /** + * Return whether the system should use the component specified by the + * {@link android:targetActivity} or + * {@link QuickAccessWalletService#getTargetActivityPendingIntent()} + * as the "quick access" , invoked directly by the system. + * If false, the system will use the built-in UI instead of the component specified + * in {@link android:targetActivity} or + * {@link QuickAccessWalletService#getTargetActivityPendingIntent()}. + */ + boolean useTargetActivityForQuickAccess(); } diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java index 2d0faad8cb13..95b51ea4dece 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java @@ -24,6 +24,7 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -67,10 +68,9 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser private final Queue mRequestQueue; private final Map mEventListeners; private boolean mIsConnected; - /** - * Timeout for active service connections (1 minute) - */ + /** Timeout for active service connections (1 minute) */ private static final long SERVICE_CONNECTION_TIMEOUT_MS = 60 * 1000; + @Nullable private IQuickAccessWalletService mService; @@ -146,7 +146,6 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser serviceCallback.onGetWalletCardsFailure(new GetWalletCardsError(null, null)); } }); - } @Override @@ -246,6 +245,25 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser return createIntent(walletActivity, packageName, ACTION_VIEW_WALLET); } + @Override + public void getWalletPendingIntent( + @NonNull @CallbackExecutor Executor executor, + @NonNull WalletPendingIntentCallback pendingIntentCallback) { + BaseCallbacks callbacks = new BaseCallbacks() { + @Override + public void onTargetActivityPendingIntentReceived(PendingIntent pendingIntent) { + executor.execute( + () -> pendingIntentCallback.onWalletPendingIntentRetrieved(pendingIntent)); + } + }; + executeApiCall(new ApiCaller("getTargetActivityPendingIntent") { + @Override + void performApiCall(IQuickAccessWalletService service) throws RemoteException { + service.onTargetActivityIntentRequested(callbacks); + } + }); + } + @Override @Nullable public Intent createWalletSettingsIntent() { @@ -330,6 +348,11 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser return mServiceInfo == null ? null : mServiceInfo.getShortcutLongLabel(mContext); } + @Override + public boolean useTargetActivityForQuickAccess() { + return mServiceInfo.getUseTargetActivityForQuickAccess(); + } + private void connect() { mHandler.post(this::connectInternal); } @@ -388,7 +411,7 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser return; } mIsConnected = false; - mContext.unbindService(/*conn=*/this); + mContext.unbindService(/*conn=*/ this); mService = null; mEventListeners.clear(); mRequestQueue.clear(); @@ -482,5 +505,9 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser public void onWalletServiceEvent(WalletServiceEvent event) { throw new IllegalStateException(); } + + public void onTargetActivityPendingIntentReceived(PendingIntent pendingIntent) { + throw new IllegalStateException(); + } } } diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java index db20a51e23ed..70ccd6fbd590 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java @@ -19,6 +19,7 @@ package android.service.quickaccesswallet; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; +import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.Build; @@ -238,6 +239,14 @@ public abstract class QuickAccessWalletService extends Service { mHandler.post(QuickAccessWalletService.this::onWalletDismissed); } + @Override + public void onTargetActivityIntentRequested( + @NonNull IQuickAccessWalletServiceCallbacks callbacks) { + mHandler.post( + () -> QuickAccessWalletService.this.onTargetActivityIntentRequestedInternal( + callbacks)); + } + public void registerWalletServiceEventListener( @NonNull WalletServiceEventListenerRequest request, @NonNull IQuickAccessWalletServiceCallbacks callback) { @@ -257,6 +266,15 @@ public abstract class QuickAccessWalletService extends Service { new GetWalletCardsCallbackImpl(request, callback, mHandler)); } + private void onTargetActivityIntentRequestedInternal( + IQuickAccessWalletServiceCallbacks callbacks) { + try { + callbacks.onTargetActivityPendingIntentReceived(getTargetActivityPendingIntent()); + } catch (RemoteException e) { + Log.w(TAG, "Error returning wallet cards", e); + } + } + @Override @Nullable public IBinder onBind(@NonNull Intent intent) { @@ -318,6 +336,11 @@ public abstract class QuickAccessWalletService extends Service { mHandler.post(() -> sendWalletServiceEventInternal(serviceEvent)); } + @Nullable + public PendingIntent getTargetActivityPendingIntent() { + return null; + } + private void sendWalletServiceEventInternal(WalletServiceEvent serviceEvent) { if (mEventListener == null) { Log.i(TAG, "No dismiss listener registered"); diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java index 0d290eee5777..cf4be739e05a 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java @@ -144,20 +144,23 @@ class QuickAccessWalletServiceInfo { private final CharSequence mShortcutShortLabel; @Nullable private final CharSequence mShortcutLongLabel; + private final boolean mUseTargetActivityForQuickAccess; private static ServiceMetadata empty() { - return new ServiceMetadata(null, null, null, null); + return new ServiceMetadata(null, null, null, null, false); } private ServiceMetadata( String targetActivity, String settingsActivity, CharSequence shortcutShortLabel, - CharSequence shortcutLongLabel) { + CharSequence shortcutLongLabel, + boolean useTargetActivityForQuickAccess) { mTargetActivity = targetActivity; mSettingsActivity = settingsActivity; mShortcutShortLabel = shortcutShortLabel; mShortcutLongLabel = shortcutLongLabel; + mUseTargetActivityForQuickAccess = useTargetActivityForQuickAccess; } } @@ -191,8 +194,11 @@ class QuickAccessWalletServiceInfo { R.styleable.QuickAccessWalletService_shortcutShortLabel); CharSequence shortcutLongLabel = afsAttributes.getText( R.styleable.QuickAccessWalletService_shortcutLongLabel); + boolean useTargetActivityForQuickAccess = afsAttributes.getBoolean( + R.styleable.QuickAccessWalletService_useTargetActivityForQuickAccess, + false); return new ServiceMetadata(targetActivity, settingsActivity, shortcutShortLabel, - shortcutLongLabel); + shortcutLongLabel, useTargetActivityForQuickAccess); } finally { if (afsAttributes != null) { afsAttributes.recycle(); @@ -271,4 +277,8 @@ class QuickAccessWalletServiceInfo { CharSequence getServiceLabel(Context context) { return mServiceInfo.loadLabel(context.getPackageManager()); } + + boolean getUseTargetActivityForQuickAccess() { + return mServiceMetadata.mUseTargetActivityForQuickAccess; + } } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index d774fd4e397a..68ed8c962ba9 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8821,7 +8821,8 @@ - + +