diff --git a/core/api/current.txt b/core/api/current.txt index 9cfc161fb7e43..c6047dc647ae7 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9015,6 +9015,7 @@ package android.companion { public final class CompanionDeviceManager { method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER, android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING, android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER, android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING, android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.Callback); + method @Nullable public android.content.IntentSender buildAssociationCancellationIntent(); method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException; method @Deprecated public void disassociate(@NonNull String); method public void disassociate(int); diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 2bd36b8675e1e..bf7043c8e41e3 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -402,6 +402,34 @@ public final class CompanionDeviceManager { } } + /** + * Cancel the current association activity. + * + *

The app should launch the returned {@code intentSender} by calling + * {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} to + * cancel the current association activity

+ * + *

Calling this API requires a uses-feature + * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest

+ * + * @return An {@link IntentSender} that the app should use to launch in order to cancel the + * current association activity + */ + @UserHandleAware + @Nullable + public IntentSender buildAssociationCancellationIntent() { + if (!checkFeaturePresent()) return null; + + try { + PendingIntent pendingIntent = mService.buildAssociationCancellationIntent( + mContext.getOpPackageName(), mContext.getUserId()); + return pendingIntent.getIntentSender(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** *

Calling this API requires a uses-feature * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest

diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl index 17e313252010f..24ef52b37dabe 100644 --- a/core/java/android/companion/ICompanionDeviceManager.aidl +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -82,4 +82,6 @@ interface ICompanionDeviceManager { void detachSystemDataTransport(String packageName, int userId, int associationId); boolean isCompanionApplicationBound(String packageName, int userId); + + PendingIntent buildAssociationCancellationIntent(in String callingPackage, int userId); } diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index 4fb575b0ab09c..9818ee78b2b18 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -45,6 +45,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; @@ -81,6 +82,7 @@ import java.util.List; * A CompanionDevice activity response for showing the available * nearby devices to be associated with. */ +@SuppressLint("LongLogTag") public class CompanionDeviceActivity extends FragmentActivity implements CompanionVendorHelperDialogFragment.CompanionVendorHelperDialogListener { private static final boolean DEBUG = false; @@ -94,6 +96,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements private static final String EXTRA_APPLICATION_CALLBACK = "application_callback"; private static final String EXTRA_ASSOCIATION_REQUEST = "association_request"; private static final String EXTRA_RESULT_RECEIVER = "result_receiver"; + private static final String EXTRA_FORCE_CANCEL_CONFIRMATION = "cancel_confirmation"; private static final String FRAGMENT_DIALOG_TAG = "fragment_dialog"; @@ -162,6 +165,12 @@ public class CompanionDeviceActivity extends FragmentActivity implements @Override public void onCreate(Bundle savedInstanceState) { if (DEBUG) Log.d(TAG, "onCreate()"); + boolean forceCancelDialog = getIntent().getBooleanExtra("cancel_confirmation", false); + // Must handle the force cancel request in onNewIntent. + if (forceCancelDialog) { + Log.i(TAG, "The confirmation does not exist, skipping the cancel request"); + finish(); + } super.onCreate(savedInstanceState); getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); @@ -195,10 +204,23 @@ public class CompanionDeviceActivity extends FragmentActivity implements @Override protected void onNewIntent(Intent intent) { + // Force cancels the CDM dialog if this activity receives another intent with + // EXTRA_FORCE_CANCEL_CONFIRMATION. + boolean forCancelDialog = intent.getBooleanExtra(EXTRA_FORCE_CANCEL_CONFIRMATION, false); + + if (forCancelDialog) { + + Log.i(TAG, "Cancelling the user confirmation"); + + cancel(false, false); + return; + } + // Handle another incoming request (while we are not done with the original - mRequest - // yet). final AssociationRequest request = requireNonNull( intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST)); + if (DEBUG) Log.d(TAG, "onNewIntent(), request=" + request); // We can only "process" one request at a time. diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java index 732e7340cf5a8..75b0df5ac6289 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java @@ -29,6 +29,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; @@ -68,6 +69,7 @@ import java.util.Objects; /** * A CompanionDevice service response for scanning nearby devices */ +@SuppressLint("LongLogTag") public class CompanionDeviceDiscoveryService extends Service { private static final boolean DEBUG = false; private static final String TAG = "CDM_CompanionDeviceDiscoveryService"; diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java index dc9144a68e4c8..0ab5a8afe907e 100644 --- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java @@ -116,6 +116,7 @@ class AssociationRequestsProcessor { private static final String EXTRA_APPLICATION_CALLBACK = "application_callback"; private static final String EXTRA_ASSOCIATION_REQUEST = "association_request"; private static final String EXTRA_RESULT_RECEIVER = "result_receiver"; + private static final String EXTRA_FORCE_CANCEL_CONFIRMATION = "cancel_confirmation"; // AssociationRequestsProcessor -> UI private static final int RESULT_CODE_ASSOCIATION_CREATED = 0; @@ -195,21 +196,7 @@ class AssociationRequestsProcessor { intent.putExtras(extras); // 2b.3. Create a PendingIntent. - final PendingIntent pendingIntent; - final long token = Binder.clearCallingIdentity(); - try { - // Using uid of the application that will own the association (usually the same - // application that sent the request) allows us to have multiple "pending" association - // requests at the same time. - // If the application already has a pending association request, that PendingIntent - // will be cancelled. - pendingIntent = PendingIntent.getActivityAsUser( - mContext, /*requestCode */ packageUid, intent, - FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, - /* options= */ null, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(token); - } + final PendingIntent pendingIntent = createPendingIntent(packageUid, intent); // 2b.4. Send the PendingIntent back to the app. try { @@ -217,6 +204,27 @@ class AssociationRequestsProcessor { } catch (RemoteException ignore) { } } + /** + * Process another AssociationRequest in CompanionDeviceActivity to cancel current dialog. + */ + PendingIntent buildAssociationCancellationIntent(@NonNull String packageName, + @UserIdInt int userId) { + requireNonNull(packageName, "Package name MUST NOT be null"); + + enforceUsesCompanionDeviceFeature(mContext, userId, packageName); + + final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId); + + final Bundle extras = new Bundle(); + extras.putBoolean(EXTRA_FORCE_CANCEL_CONFIRMATION, true); + + final Intent intent = new Intent(); + intent.setComponent(ASSOCIATION_REQUEST_APPROVAL_ACTIVITY); + intent.putExtras(extras); + + return createPendingIntent(packageUid, intent); + } + private void processAssociationRequestApproval(@NonNull AssociationRequest request, @NonNull IAssociationRequestCallback callback, @NonNull ResultReceiver resultReceiver, @Nullable MacAddress macAddress) { @@ -286,6 +294,27 @@ class AssociationRequestsProcessor { return !isRoleHolder; } + private PendingIntent createPendingIntent(int packageUid, Intent intent) { + final PendingIntent pendingIntent; + final long token = Binder.clearCallingIdentity(); + + // Using uid of the application that will own the association (usually the same + // application that sent the request) allows us to have multiple "pending" association + // requests at the same time. + // If the application already has a pending association request, that PendingIntent + // will be cancelled except application wants to cancel the request by the system. + try { + pendingIntent = PendingIntent.getActivityAsUser( + mContext, /*requestCode */ packageUid, intent, + FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, + /* options= */ null, UserHandle.CURRENT); + } finally { + Binder.restoreCallingIdentity(token); + } + + return pendingIntent; + } + private final ResultReceiver mOnRequestConfirmationReceiver = new ResultReceiver(Handler.getMain()) { @Override diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 85d3140424961..56a5a8f829b7b 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -548,6 +548,18 @@ public class CompanionDeviceManagerService extends SystemService { request, packageName, userId, callback); } + @Override + public PendingIntent buildAssociationCancellationIntent(String packageName, + int userId) throws RemoteException { + Slog.i(TAG, "buildAssociationCancellationIntent() " + + "package=u" + userId + "/" + packageName); + enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName, + "build association cancellation intent"); + + return mAssociationRequestsProcessor.buildAssociationCancellationIntent( + packageName, userId); + } + @Override public List getAssociations(String packageName, int userId) { enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,