diff options
| -rw-r--r-- | core/api/current.txt | 6 | ||||
| -rw-r--r-- | core/java/android/companion/CompanionDeviceManager.java | 72 | ||||
| -rw-r--r-- | core/java/android/companion/DeviceNotAssociatedException.java | 31 | ||||
| -rw-r--r-- | core/java/android/companion/ICompanionDeviceManager.aidl | 4 | ||||
| -rw-r--r-- | core/java/android/os/RemoteException.java | 5 | ||||
| -rw-r--r-- | core/java/android/util/ExceptionUtils.java | 2 | ||||
| -rw-r--r-- | core/res/AndroidManifest.xml | 6 | ||||
| -rw-r--r-- | services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java | 38 |
8 files changed, 164 insertions, 0 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 026e155fa258..1770008b0843 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -141,6 +141,7 @@ package android { field public static final String REQUEST_DELETE_PACKAGES = "android.permission.REQUEST_DELETE_PACKAGES"; field public static final String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"; field public static final String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES"; + field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"; field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY"; field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES"; field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE"; @@ -9520,6 +9521,8 @@ package android.companion { method @NonNull public java.util.List<java.lang.String> getAssociations(); method 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 @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException; field public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE"; } @@ -9540,6 +9543,9 @@ package android.companion { public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable { } + public class DeviceNotAssociatedException extends java.lang.Exception { + } + public final class WifiDeviceFilter implements android.companion.DeviceFilter<android.net.wifi.ScanResult> { method public int describeContents(); method public void writeToParcel(@NonNull android.os.Parcel, int); diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index c3c270e52eb6..59646f106db5 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -34,6 +34,7 @@ import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; import android.service.notification.NotificationListenerService; +import android.util.ExceptionUtils; import android.util.Log; import java.util.Collections; @@ -321,6 +322,77 @@ public final class CompanionDeviceManager { } } + /** + * Register to receive callbacks whenever the associated device comes in and out of range. + * + * The provided device must be {@link #associate associated} with the calling app before + * calling this method. + * + * Caller must implement a single {@link CompanionDeviceService} which will be bound to and + * receive callbacks to {@link CompanionDeviceService#onDeviceAppeared} and + * {@link CompanionDeviceService#onDeviceDisappeared}. + * The app doesn't need to remain running in order to receive its callbacks. + * + * Calling app must declare uses-permission + * {@link android.Manifest.permission#REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE}. + * + * Calling app must check for feature presence of + * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} before calling this API. + * + * @param deviceAddress a previously-associated companion device's address + * + * @throws DeviceNotAssociatedException if the given device was not previously associated + * with this app. + */ + @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) + public void startObservingDevicePresence(@NonNull String deviceAddress) + throws DeviceNotAssociatedException { + if (!checkFeaturePresent()) { + return; + } + Objects.requireNonNull(deviceAddress, "address cannot be null"); + try { + mService.registerDevicePresenceListenerService( + mContext.getPackageName(), deviceAddress); + } catch (RemoteException e) { + ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregister for receiving callbacks whenever the associated device comes in and out of range. + * + * The provided device must be {@link #associate associated} with the calling app before + * calling this method. + * + * Calling app must declare uses-permission + * {@link android.Manifest.permission#REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE}. + * + * Calling app must check for feature presence of + * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} before calling this API. + * + * @param deviceAddress a previously-associated companion device's address + * + * @throws DeviceNotAssociatedException if the given device was not previously associated + * with this app. + */ + @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) + public void stopObservingDevicePresence(@NonNull String deviceAddress) + throws DeviceNotAssociatedException { + if (!checkFeaturePresent()) { + return; + } + Objects.requireNonNull(deviceAddress, "address cannot be null"); + try { + mService.unregisterDevicePresenceListenerService( + mContext.getPackageName(), deviceAddress); + } catch (RemoteException e) { + ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class); + throw e.rethrowFromSystemServer(); + } + } + private boolean checkFeaturePresent() { boolean featurePresent = mService != null; if (!featurePresent && DEBUG) { diff --git a/core/java/android/companion/DeviceNotAssociatedException.java b/core/java/android/companion/DeviceNotAssociatedException.java new file mode 100644 index 000000000000..bebb6c9ff7eb --- /dev/null +++ b/core/java/android/companion/DeviceNotAssociatedException.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 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.Nullable; + +/** + * An exception for a case when a given device was not + * {@link CompanionDeviceManager#associate associated} to the calling app. + */ +public class DeviceNotAssociatedException extends Exception { + /** @hide */ + public DeviceNotAssociatedException(@Nullable String deviceName) { + super("Device not associated with the current app: " + deviceName); + } +} diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl index bcb9be80e6a4..527d8df94ea0 100644 --- a/core/java/android/companion/ICompanionDeviceManager.aidl +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -45,4 +45,8 @@ interface ICompanionDeviceManager { boolean isDeviceAssociatedForWifiConnection(in String packageName, in String macAddress, int userId); + + void registerDevicePresenceListenerService(in String packageName, in String deviceAddress); + + void unregisterDevicePresenceListenerService(in String packageName, in String deviceAddress); } diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java index 98c66d1beaf4..e9fc2f38072b 100644 --- a/core/java/android/os/RemoteException.java +++ b/core/java/android/os/RemoteException.java @@ -37,6 +37,11 @@ public class RemoteException extends AndroidException { super(message, cause, enableSuppression, writableStackTrace); } + /** @hide */ + public RemoteException(Throwable cause) { + this(cause.getMessage(), cause, true, false); + } + /** * Rethrow this as an unchecked runtime exception. * <p> diff --git a/core/java/android/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java index 1a397b39ef3c..4b511acc280f 100644 --- a/core/java/android/util/ExceptionUtils.java +++ b/core/java/android/util/ExceptionUtils.java @@ -98,4 +98,6 @@ public class ExceptionUtils { } return t; } + + }
\ No newline at end of file diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 648ea390369f..ef7edc2f2dd2 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3969,6 +3969,12 @@ <permission android:name="android.permission.MANAGE_COMPANION_DEVICES" android:protectionLevel="signature" /> + <!-- Allows an application to subscribe to notifications about the presence status change + of their associated companion device + --> + <permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE" + android:protectionLevel="normal" /> + <!-- @SystemApi Allows an application to use SurfaceFlinger's low level features. <p>Not for use by third-party applications. @hide diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 0a80b02a964c..e6e52de0440f 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -40,6 +40,7 @@ import android.bluetooth.BluetoothDevice; import android.companion.Association; import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; +import android.companion.DeviceNotAssociatedException; import android.companion.ICompanionDeviceDiscoveryService; import android.companion.ICompanionDeviceManager; import android.companion.IFindDeviceCallback; @@ -486,6 +487,43 @@ public class CompanionDeviceManagerService extends SystemService implements Bind a -> Objects.equals(a.getDeviceMacAddress(), macAddress)); } + @Override + public void registerDevicePresenceListenerService( + String packageName, String deviceAddress) + throws RemoteException { + checkCanRegisterObserverService(packageName, deviceAddress); + + //TODO(eugenesusla) implement + } + + @Override + public void unregisterDevicePresenceListenerService( + String packageName, String deviceAddress) + throws RemoteException { + checkCanRegisterObserverService(packageName, deviceAddress); + + //TODO(eugenesusla) implement + } + + private void checkCanRegisterObserverService(String packageName, String deviceAddress) + throws RemoteException { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE, + "[un]registerDevicePresenceListenerService"); + checkCallerIsSystemOr(packageName); + + int userId = getCallingUserId(); + Set<Association> deviceAssociations = CollectionUtils.filter( + getAllAssociations(userId, packageName), + association -> deviceAddress.equals(association.getDeviceMacAddress())); + + if (deviceAssociations.isEmpty()) { + throw new RemoteException(new DeviceNotAssociatedException("App " + packageName + + " is not associated with device " + deviceAddress + + " for user " + userId)); + } + } + private void checkCanCallNotificationApi(String callingPackage) throws RemoteException { checkCallerIsSystemOr(callingPackage); int userId = getCallingUserId(); |