Add an API for cancelling current CDM activity

Test: atest CtsCompanionDeviceManagerCoreTestCases
      atest CtsCompanionDeviceManagerUiAutomationTestCases
      atest CtsOsTestCases:CompanionDeviceManagerTest
Bug: 242012556
Change-Id: If76ed8ccaef1982d7bd1f188f112c35338352f78
This commit is contained in:
Evan Chen
2022-08-24 00:09:10 +00:00
parent 8ffc7346a3
commit 3b70638477
7 changed files with 111 additions and 15 deletions

View File

@@ -9014,6 +9014,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);

View File

@@ -402,6 +402,34 @@ public final class CompanionDeviceManager {
}
}
/**
* Cancel the current association activity.
*
* <p>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</p>
*
* <p>Calling this API requires a uses-feature
* {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>
*
* @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();
}
}
/**
* <p>Calling this API requires a uses-feature
* {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<AssociationInfo> getAssociations(String packageName, int userId) {
enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,