diff --git a/core/api/current.txt b/core/api/current.txt index e3f4a90f9f670..fd11ea485b832 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9010,7 +9010,8 @@ package android.companion { method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName); method public void requestNotificationAccess(android.content.ComponentName); method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException; - method public void startSystemDataTransfer(int) throws android.companion.DeviceNotAssociatedException; + method @Deprecated public void startSystemDataTransfer(int) throws android.companion.DeviceNotAssociatedException; + method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver) throws android.companion.DeviceNotAssociatedException; method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException; field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION"; field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE"; @@ -9036,6 +9037,9 @@ package android.companion { field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService"; } + public class CompanionException extends java.lang.RuntimeException { + } + public interface DeviceFilter extends android.os.Parcelable { } diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index b6b860d42ccb9..1b51faf3d4299 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -38,6 +38,7 @@ import android.content.IntentSender; import android.content.pm.PackageManager; import android.net.MacAddress; import android.os.Handler; +import android.os.OutcomeReceiver; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.UserHandle; @@ -964,12 +965,44 @@ public final class CompanionDeviceManager { * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association * of the companion device recorded by CompanionDeviceManager * @throws DeviceNotAssociatedException Exception if the companion device is not associated + * + * @deprecated Use {@link #startSystemDataTransfer(int, Executor, OutcomeReceiver)} instead. */ + @Deprecated @UserHandleAware public void startSystemDataTransfer(int associationId) throws DeviceNotAssociatedException { try { mService.startSystemDataTransfer(mContext.getOpPackageName(), mContext.getUserId(), - associationId); + associationId, null); + } catch (RemoteException e) { + ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Start system data transfer which has been previously approved by the user. + * + *

Before calling this method, the app needs to make sure there's a communication channel + * between two devices, and has prompted user consent dialogs built by one of these methods: + * {@link #buildPermissionTransferUserConsentIntent(int)}. + * The transfer may fail if the communication channel is disconnected during the transfer.

+ * + * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association + * of the companion device recorded by CompanionDeviceManager + * @param executor The executor which will be used to invoke the result callback. + * @param result The callback to notify the app of the result of the system data transfer. + * @throws DeviceNotAssociatedException Exception if the companion device is not associated + */ + @UserHandleAware + public void startSystemDataTransfer( + int associationId, + @NonNull Executor executor, + @NonNull OutcomeReceiver result) + throws DeviceNotAssociatedException { + try { + mService.startSystemDataTransfer(mContext.getOpPackageName(), mContext.getUserId(), + associationId, new SystemDataTransferCallbackProxy(executor, result)); } catch (RemoteException e) { ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); throw e.rethrowFromSystemServer(); @@ -1045,6 +1078,27 @@ public final class CompanionDeviceManager { } } + private static class SystemDataTransferCallbackProxy extends ISystemDataTransferCallback.Stub { + private final Executor mExecutor; + private final OutcomeReceiver mCallback; + + private SystemDataTransferCallbackProxy(Executor executor, + OutcomeReceiver callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onResult() { + mExecutor.execute(() -> mCallback.onResult(null)); + } + + @Override + public void onError(String error) { + mExecutor.execute(() -> mCallback.onError(new CompanionException(error))); + } + } + /** * Representation of an active system data transport. *

diff --git a/core/java/android/companion/CompanionException.java b/core/java/android/companion/CompanionException.java new file mode 100644 index 0000000000000..fb78e8df446e4 --- /dev/null +++ b/core/java/android/companion/CompanionException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 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 android.companion; + +import android.annotation.NonNull; + +/** + * {@code CompanionException} can be thrown during the companion system data transfer process. + */ +public class CompanionException extends RuntimeException { + /** @hide */ + public CompanionException(@NonNull String message) { + super(message); + } +} diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl index ab7dc09cf4595..42f908396799d 100644 --- a/core/java/android/companion/ICompanionDeviceManager.aidl +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -19,6 +19,7 @@ package android.companion; import android.app.PendingIntent; import android.companion.IAssociationRequestCallback; import android.companion.IOnAssociationsChangedListener; +import android.companion.ISystemDataTransferCallback; import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.content.ComponentName; @@ -75,7 +76,8 @@ interface ICompanionDeviceManager { PendingIntent buildPermissionTransferUserConsentIntent(String callingPackage, int userId, int associationId); - void startSystemDataTransfer(String packageName, int userId, int associationId); + void startSystemDataTransfer(String packageName, int userId, int associationId, + in ISystemDataTransferCallback callback); void attachSystemDataTransport(String packageName, int userId, int associationId, in ParcelFileDescriptor fd); diff --git a/core/java/android/companion/ISystemDataTransferCallback.aidl b/core/java/android/companion/ISystemDataTransferCallback.aidl new file mode 100644 index 0000000000000..1ae5376942e25 --- /dev/null +++ b/core/java/android/companion/ISystemDataTransferCallback.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 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 per missions and + * limitations under the License. + */ + +package android.companion; + +/** @hide */ +interface ISystemDataTransferCallback { + oneway void onResult(); + + oneway void onError(String error); +} \ No newline at end of file diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 90c4002542bfc..c3545619bcb93 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -62,6 +62,7 @@ import android.companion.DeviceNotAssociatedException; import android.companion.IAssociationRequestCallback; import android.companion.ICompanionDeviceManager; import android.companion.IOnAssociationsChangedListener; +import android.companion.ISystemDataTransferCallback; import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; @@ -725,9 +726,10 @@ public class CompanionDeviceManagerService extends SystemService { } @Override - public void startSystemDataTransfer(String packageName, int userId, int associationId) { + public void startSystemDataTransfer(String packageName, int userId, int associationId, + ISystemDataTransferCallback callback) { mSystemDataTransferProcessor.startSystemDataTransfer(packageName, userId, - associationId); + associationId, callback); } @Override diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java index a839492dd7ac1..7eede552ac712 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java @@ -29,6 +29,7 @@ import android.annotation.UserIdInt; import android.app.PendingIntent; import android.companion.AssociationInfo; import android.companion.DeviceNotAssociatedException; +import android.companion.ISystemDataTransferCallback; import android.companion.datatransfer.PermissionSyncRequest; import android.companion.datatransfer.SystemDataTransferRequest; import android.content.ComponentName; @@ -167,8 +168,11 @@ public class SystemDataTransferProcessor { /** * Start system data transfer. It should first try to establish a secure channel and then sync * system data. + * + * TODO: execute callback when the transfer finishes successfully or with errors. */ - public void startSystemDataTransfer(String packageName, int userId, int associationId) { + public void startSystemDataTransfer(String packageName, int userId, int associationId, + ISystemDataTransferCallback callback) { Slog.i(LOG_TAG, "Start system data transfer for package [" + packageName + "] userId [" + userId + "] associationId [" + associationId + "]");