Register PackageMonitor for CompanionDeviceManagerService
1. On package removed -> remove all its associations
2. On package updated -> if had associations, update special access permission
in accordance with (potentially changed) permission entries in manifest
Bug: 30932767
Test: 1. Remove app, and ensure xml entries for it got removed.
2. adb install new version of app without special permissions in manifest, and
ensure whitelist removal method got called
Change-Id: I87261c05ddcf40a18332d160b44ee2f8284df5e4
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 56f5d44..bb844a3 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -23,6 +23,7 @@
import android.provider.OneTimeUseBuilder;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
@@ -47,7 +48,7 @@
private AssociationRequest(
boolean singleDevice, @Nullable List<DeviceFilter<?>> deviceFilters) {
this.mSingleDevice = singleDevice;
- this.mDeviceFilters = ArrayUtils.emptyIfNull(deviceFilters);
+ this.mDeviceFilters = CollectionUtils.emptyIfNull(deviceFilters);
}
private AssociationRequest(Parcel in) {
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index 0f16b7b..1d8df7f 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -31,6 +31,7 @@
import android.provider.OneTimeUseBuilder;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
@@ -53,8 +54,8 @@
List<ParcelUuid> serviceUuidMasks) {
mNamePattern = namePattern;
mAddress = address;
- mServiceUuids = ArrayUtils.emptyIfNull(serviceUuids);
- mServiceUuidMasks = ArrayUtils.emptyIfNull(serviceUuidMasks);
+ mServiceUuids = CollectionUtils.emptyIfNull(serviceUuids);
+ mServiceUuidMasks = CollectionUtils.emptyIfNull(serviceUuidMasks);
}
private BluetoothDeviceFilter(Parcel in) {
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 0136979..ac8aee6c 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -26,6 +26,7 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
+import java.util.function.Supplier;
/**
* Base class for a remotable object, the core part of a lightweight
@@ -246,6 +247,36 @@
public static final native void restoreCallingIdentity(long token);
/**
+ * Convenience method for running the provided action enclosed in
+ * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity}
+ *
+ * @hide
+ */
+ public static final void withCleanCallingIdentity(Runnable action) {
+ long callingIdentity = clearCallingIdentity();
+ try {
+ action.run();
+ } finally {
+ restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ /**
+ * Convenience method for running the provided action enclosed in
+ * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity} returning the result
+ *
+ * @hide
+ */
+ public static final <T> T withCleanCallingIdentity(Supplier<T> action) {
+ long callingIdentity = clearCallingIdentity();
+ try {
+ return action.get();
+ } finally {
+ restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ /**
* Sets the native thread-local StrictMode policy mask.
*
* <p>The StrictMode settings are kept in two places: a Java-level
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index c432519..a4d5c6f 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -16,6 +16,9 @@
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
@@ -24,6 +27,7 @@
int mPriority;
int mTid = -1;
Looper mLooper;
+ private @Nullable Handler mHandler;
public HandlerThread(String name) {
super(name);
@@ -86,6 +90,18 @@
}
/**
+ * @return a shared {@link Handler} associated with this thread
+ * @hide
+ */
+ @NonNull
+ public Handler getThreadHandler() {
+ if (mHandler == null) {
+ mHandler = new Handler(getLooper());
+ }
+ return mHandler;
+ }
+
+ /**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index d0fbe7c..f4dd5a6 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -31,7 +31,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
-import java.util.function.Function;
/**
* ArrayUtils contains some methods that you can call to find out
@@ -237,35 +236,6 @@
return false;
}
- @NonNull
- public static <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
- if (isEmpty(list)) return Collections.emptyList();
- ArrayList<T> result = null;
- for (int i = 0; i < list.size(); i++) {
- final Object item = list.get(i);
- if (c.isInstance(item)) {
- result = add(result, (T) item);
- }
- }
- return emptyIfNull(result);
- }
-
- public static <T> boolean any(@Nullable List<T> items,
- java.util.function.Predicate<T> predicate) {
- return find(items, predicate) != null;
- }
-
- @Nullable
- public static <T> T find(@Nullable List<T> items,
- java.util.function.Predicate<T> predicate) {
- if (isEmpty(items)) return null;
- for (int i = 0; i < items.size(); i++) {
- final T item = items.get(i);
- if (predicate.test(item)) return item;
- }
- return null;
- }
-
public static long total(@Nullable long[] array) {
long total = 0;
if (array != null) {
@@ -504,29 +474,6 @@
}
}
- public static int size(@Nullable Collection<?> cur) {
- return cur != null ? cur.size() : 0;
- }
-
- public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
- Function<? super I, ? extends O> f) {
- if (cur == null || cur.isEmpty()) return Collections.emptyList();
- final ArrayList<O> result = new ArrayList<>();
- for (int i = 0; i < cur.size(); i++) {
- result.add(f.apply(cur.get(i)));
- }
- return result;
- }
-
- /**
- * Returns the given list, or an immutable empty list if the provided list is null
- *
- * @see Collections#emptyList
- */
- public static @NonNull <T> List<T> emptyIfNull(@Nullable List<T> cur) {
- return cur == null ? Collections.emptyList() : cur;
- }
-
public static <T> boolean contains(@Nullable Collection<T> cur, T val) {
return (cur != null) ? cur.contains(val) : false;
}
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
new file mode 100644
index 0000000..287f68c
--- /dev/null
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 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 com.android.internal.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+/**
+ * Utility methods for dealing with (typically {@link Nullable}) {@link Collection}s
+ *
+ * Unless a method specifies otherwise, a null value for a collection is treated as an empty
+ * collection of that type.
+ */
+public class CollectionUtils {
+ private CollectionUtils() { /* cannot be instantiated */ }
+
+ /**
+ * Returns a list of items from the provided list that match the given condition.
+ *
+ * This is similar to {@link Stream#filter} but without the overhead of creating an intermediate
+ * {@link Stream} instance
+ */
+ public static @NonNull <T> List<T> filter(@Nullable List<T> list,
+ java.util.function.Predicate<? super T> predicate) {
+ ArrayList<T> result = null;
+ for (int i = 0; i < size(list); i++) {
+ final T item = list.get(i);
+ if (predicate.test(item)) {
+ result = ArrayUtils.add(result, item);
+ }
+ }
+ return emptyIfNull(result);
+ }
+
+ /**
+ * Returns a list of items resulting from applying the given function to each element of the
+ * provided list.
+ *
+ * The resulting list will have the same {@link #size} as the input one.
+ *
+ * This is similar to {@link Stream#map} but without the overhead of creating an intermediate
+ * {@link Stream} instance
+ */
+ public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
+ Function<? super I, ? extends O> f) {
+ if (cur == null || cur.isEmpty()) return Collections.emptyList();
+ final ArrayList<O> result = new ArrayList<>();
+ for (int i = 0; i < cur.size(); i++) {
+ result.add(f.apply(cur.get(i)));
+ }
+ return result;
+ }
+
+ /**
+ * Returns the given list, or an immutable empty list if the provided list is null
+ *
+ * This can be used to guaranty null-safety without paying the price of extra allocations
+ *
+ * @see Collections#emptyList
+ */
+ public static @NonNull <T> List<T> emptyIfNull(@Nullable List<T> cur) {
+ return cur == null ? Collections.emptyList() : cur;
+ }
+
+ /**
+ * Returns the size of the given list, or 0 if the list is null
+ */
+ public static int size(@Nullable Collection<?> cur) {
+ return cur != null ? cur.size() : 0;
+ }
+
+ /**
+ * Returns the elements of the given list that are of type {@code c}
+ */
+ public static @NonNull <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
+ if (ArrayUtils.isEmpty(list)) return Collections.emptyList();
+ ArrayList<T> result = null;
+ for (int i = 0; i < list.size(); i++) {
+ final Object item = list.get(i);
+ if (c.isInstance(item)) {
+ result = ArrayUtils.add(result, (T) item);
+ }
+ }
+ return emptyIfNull(result);
+ }
+
+ /**
+ * Returns whether there exists at least one element in the list for which
+ * condition {@code predicate} is true
+ */
+ public static <T> boolean any(@Nullable List<T> items,
+ java.util.function.Predicate<T> predicate) {
+ return find(items, predicate) != null;
+ }
+
+ /**
+ * Returns the first element from the list for which
+ * condition {@code predicate} is true, or null if there is no such element
+ */
+ public static @Nullable <T> T find(@Nullable List<T> items,
+ java.util.function.Predicate<T> predicate) {
+ if (ArrayUtils.isEmpty(items)) return null;
+ for (int i = 0; i < items.size(); i++) {
+ final T item = items.get(i);
+ if (predicate.test(item)) return item;
+ }
+ return null;
+ }
+}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index e1e60bb..e49463f 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -35,9 +35,7 @@
import android.bluetooth.le.ScanSettings;
import android.companion.AssociationRequest;
import android.companion.BluetoothDeviceFilter;
-import android.companion.BluetoothDeviceFilterUtils;
import android.companion.BluetoothLEDeviceFilter;
-import android.companion.CompanionDeviceManager;
import android.companion.DeviceFilter;
import android.companion.ICompanionDeviceDiscoveryService;
import android.companion.ICompanionDeviceDiscoveryServiceCallback;
@@ -60,7 +58,7 @@
import android.widget.ArrayAdapter;
import android.widget.TextView;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
@@ -185,10 +183,10 @@
mRequest = request;
mFilters = request.getDeviceFilters();
- mWifiFilters = ArrayUtils.filter(mFilters, WifiDeviceFilter.class);
- mBluetoothFilters = ArrayUtils.filter(mFilters, BluetoothDeviceFilter.class);
- mBLEFilters = ArrayUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
- mBLEScanFilters = ArrayUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
+ mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
+ mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
+ mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
+ mBLEScanFilters = CollectionUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
reset();
@@ -357,7 +355,7 @@
public static <T extends Parcelable> DeviceFilterPair<T> findMatch(
T dev, @Nullable List<? extends DeviceFilter<T>> filters) {
if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null);
- final DeviceFilter<T> matchingFilter = ArrayUtils.find(filters, (f) -> f.matches(dev));
+ final DeviceFilter<T> matchingFilter = CollectionUtils.find(filters, (f) -> f.matches(dev));
return matchingFilter != null ? new DeviceFilterPair<>(dev, matchingFilter) : null;
}
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 44ca6a9..3e2dae5 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -1136,6 +1136,9 @@
private final class BinderService extends IDeviceIdleController.Stub {
@Override public void addPowerSaveWhitelistApp(String name) {
+ if (DEBUG) {
+ Slog.i(TAG, "addPowerSaveWhitelistApp(name = " + name + ")");
+ }
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
long ident = Binder.clearCallingIdentity();
@@ -1147,6 +1150,9 @@
}
@Override public void removePowerSaveWhitelistApp(String name) {
+ if (DEBUG) {
+ Slog.i(TAG, "removePowerSaveWhitelistApp(name = " + name + ")");
+ }
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
long ident = Binder.clearCallingIdentity();
diff --git a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
index e6e2cb3..9356dac 100644
--- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
+++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
@@ -46,7 +46,10 @@
import android.util.Slog;
import android.util.Xml;
+import com.android.internal.content.PackageMonitor;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
+import com.android.server.FgThread;
import com.android.server.SystemService;
import org.xmlpull.v1.XmlPullParser;
@@ -86,10 +89,35 @@
private final CompanionDeviceManagerImpl mImpl;
private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
+ private IDeviceIdleController mIdleController;
public CompanionDeviceManagerService(Context context) {
super(context);
mImpl = new CompanionDeviceManagerImpl();
+ mIdleController = IDeviceIdleController.Stub.asInterface(
+ ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+ registerPackageMonitor();
+ }
+
+ private void registerPackageMonitor() {
+ new PackageMonitor() {
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ updateAssociations(
+ as -> CollectionUtils.filter(as,
+ a -> !Objects.equals(a.companionAppPackage, packageName)),
+ getChangingUserId());
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ int userId = getChangingUserId();
+ if (!ArrayUtils.isEmpty(readAllAssociations(userId, packageName))) {
+ updateSpecialAccessPermissionForAssociatedPackage(packageName, userId);
+ }
+ }
+
+ }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true);
}
@Override
@@ -124,9 +152,9 @@
@Override
public List<String> getAssociations(String callingPackage) {
- return ArrayUtils.map(
+ return CollectionUtils.map(
readAllAssociations(getUserId(), callingPackage),
- (a) -> a.deviceAddress);
+ a -> a.deviceAddress);
}
@Override
@@ -178,43 +206,55 @@
@Override
public void onDeviceSelected(String packageName, int userId, String deviceAddress) {
//TODO unbind
- grantSpecialAccessPermissionsIfNeeded(packageName, userId);
+ updateSpecialAccessPermissionForAssociatedPackage(packageName, userId);
recordAssociation(packageName, deviceAddress);
}
};
}
- private void grantSpecialAccessPermissionsIfNeeded(String packageName, int userId) {
- final long identity = Binder.clearCallingIdentity();
- final PackageInfo packageInfo;
- try {
+ private void updateSpecialAccessPermissionForAssociatedPackage(String packageName, int userId) {
+ PackageInfo packageInfo = getPackageInfo(packageName, userId);
+ if (packageInfo == null) {
+ return;
+ }
+
+ Binder.withCleanCallingIdentity(() -> {
try {
- packageInfo = getContext().getPackageManager().getPackageInfoAsUser(
- packageName, PackageManager.GET_PERMISSIONS, userId);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(LOG_TAG, "Error granting special access permissions to package:"
- + packageName, e);
- return;
- }
- if (ArrayUtils.contains(packageInfo.requestedPermissions,
- Manifest.permission.RUN_IN_BACKGROUND)) {
- IDeviceIdleController idleController = IDeviceIdleController.Stub.asInterface(
- ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
- try {
- idleController.addPowerSaveWhitelistApp(packageName);
- } catch (RemoteException e) {
- /* ignore - local call */
+ if (ArrayUtils.contains(packageInfo.requestedPermissions,
+ Manifest.permission.RUN_IN_BACKGROUND)) {
+ mIdleController.addPowerSaveWhitelistApp(packageInfo.packageName);
+ } else {
+ mIdleController.removePowerSaveWhitelistApp(packageInfo.packageName);
}
+ } catch (RemoteException e) {
+ /* ignore - local call */
}
+
+ NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext());
if (ArrayUtils.contains(packageInfo.requestedPermissions,
Manifest.permission.USE_DATA_IN_BACKGROUND)) {
- NetworkPolicyManager.from(getContext()).addUidPolicy(
+ networkPolicyManager.addUidPolicy(
+ packageInfo.applicationInfo.uid,
+ NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+ } else {
+ networkPolicyManager.removeUidPolicy(
packageInfo.applicationInfo.uid,
NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
}
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ });
+ }
+
+ @Nullable
+ private PackageInfo getPackageInfo(String packageName, int userId) {
+ return Binder.withCleanCallingIdentity(() -> {
+ try {
+ return getContext().getPackageManager().getPackageInfoAsUser(
+ packageName, PackageManager.GET_PERMISSIONS, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(LOG_TAG, "Failed to get PackageInfo for package " + packageName, e);
+ return null;
+ }
+ });
}
private void recordAssociation(String priviledgedPackage, String deviceAddress) {
@@ -222,13 +262,16 @@
new Association(getUserId(), deviceAddress, priviledgedPackage)));
}
- private void updateAssociations(
- Function<ArrayList<Association>, ArrayList<Association>> update) {
- final int userId = getUserId();
+ private void updateAssociations(Function<ArrayList<Association>, List<Association>> update) {
+ updateAssociations(update, getUserId());
+ }
+
+ private void updateAssociations(Function<ArrayList<Association>, List<Association>> update,
+ int userId) {
final AtomicFile file = getStorageFileForUser(userId);
synchronized (file) {
final ArrayList<Association> old = readAllAssociations(userId);
- final ArrayList<Association> associations = update.apply(old);
+ final List<Association> associations = update.apply(old);
if (Objects.equals(old, associations)) return;
file.write((out) -> {
@@ -239,7 +282,7 @@
xml.startDocument(null, true);
xml.startTag(null, XML_TAG_ASSOCIATIONS);
- for (int i = 0; i < ArrayUtils.size(associations); i++) {
+ for (int i = 0; i < CollectionUtils.size(associations); i++) {
Association association = associations.get(i);
xml.startTag(null, XML_TAG_ASSOCIATION)
.attribute(null, XML_ATTR_PACKAGE, association.companionAppPackage)