summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt6
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java72
-rw-r--r--core/java/android/companion/DeviceNotAssociatedException.java31
-rw-r--r--core/java/android/companion/ICompanionDeviceManager.aidl4
-rw-r--r--core/java/android/os/RemoteException.java5
-rw-r--r--core/java/android/util/ExceptionUtils.java2
-rw-r--r--core/res/AndroidManifest.xml6
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java38
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();