OSDN Git Service

Register PackageMonitor for CompanionDeviceManagerService
authorEugene Susla <eugenesusla@google.com>
Mon, 13 Mar 2017 19:57:58 +0000 (12:57 -0700)
committerEugene Susla <eugenesusla@google.com>
Mon, 20 Mar 2017 20:38:58 +0000 (13:38 -0700)
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

core/java/android/companion/AssociationRequest.java
core/java/android/companion/BluetoothDeviceFilter.java
core/java/android/os/Binder.java
core/java/android/os/HandlerThread.java
core/java/com/android/internal/util/ArrayUtils.java
core/java/com/android/internal/util/CollectionUtils.java [new file with mode: 0644]
packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
services/core/java/com/android/server/DeviceIdleController.java
services/print/java/com/android/server/print/CompanionDeviceManagerService.java

index 56f5d44..bb844a3 100644 (file)
@@ -23,6 +23,7 @@ import android.os.Parcelable;
 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 @@ public final class AssociationRequest implements Parcelable {
     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) {
index 0f16b7b..1d8df7f 100644 (file)
@@ -31,6 +31,7 @@ import android.os.ParcelUuid;
 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 @@ public final class BluetoothDeviceFilter implements DeviceFilter<BluetoothDevice
             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) {
index 0136979..ac8aee6 100644 (file)
@@ -26,6 +26,7 @@ import java.io.FileOutputStream;
 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 class Binder implements IBinder {
     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
index c432519..a4d5c6f 100644 (file)
@@ -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 @@ public class HandlerThread extends Thread {
     int mPriority;
     int mTid = -1;
     Looper mLooper;
+    private @Nullable Handler mHandler;
 
     public HandlerThread(String name) {
         super(name);
@@ -86,6 +90,18 @@ public class HandlerThread extends Thread {
     }
 
     /**
+     * @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
index d0fbe7c..f4dd5a6 100644 (file)
@@ -31,7 +31,6 @@ import java.util.Collection;
 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 @@ public class ArrayUtils {
         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 class ArrayUtils {
         }
     }
 
-    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 (file)
index 0000000..287f68c
--- /dev/null
@@ -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;
+    }
+}
index e1e60bb..e49463f 100644 (file)
@@ -35,9 +35,7 @@ import android.bluetooth.le.ScanResult;
 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.view.ViewGroup;
 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 @@ public class DeviceDiscoveryService extends Service {
         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 class DeviceDiscoveryService extends Service {
         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;
         }
 
index 44ca6a9..3e2dae5 100644 (file)
@@ -1136,6 +1136,9 @@ public class DeviceIdleController extends SystemService
 
     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 @@ public class DeviceIdleController extends SystemService
         }
 
         @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();
index e6e2cb3..9356dac 100644 (file)
@@ -46,7 +46,10 @@ import android.util.ExceptionUtils;
 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 @@ public class CompanionDeviceManagerService extends SystemService {
 
     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 @@ public class CompanionDeviceManagerService extends SystemService {
 
         @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 @@ public class CompanionDeviceManagerService extends SystemService {
             @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 @@ public class CompanionDeviceManagerService extends SystemService {
                 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 @@ public class CompanionDeviceManagerService extends SystemService {
                     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)