Merge "Add Listeners to PlatformCompat"
am: e14d7099a9

Change-Id: Id187394dc3f52f560faac6b9c73dea40119a5eff
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index 8762435..95582f7 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -38,6 +38,20 @@
  */
 public final class CompatChange extends CompatibilityChangeInfo {
 
+    /**
+     * Callback listener for when compat changes are updated for a package.
+     * See {@link #registerListener(ChangeListener)} for more details.
+     */
+    public interface ChangeListener {
+        /**
+         * Called upon an override change for packageName and the change this listener is
+         * registered for. Called before the app is killed.
+         */
+        void onCompatChange(String packageName);
+    }
+
+    ChangeListener mListener = null;
+
     private Map<String, Boolean> mPackageOverrides;
 
     public CompatChange(long changeId) {
@@ -64,6 +78,15 @@
                 change.getDisabled());
     }
 
+    void registerListener(ChangeListener listener) {
+        if (mListener != null) {
+            throw new IllegalStateException(
+                    "Listener for change " + toString() + " already registered.");
+        }
+        mListener = listener;
+    }
+
+
     /**
      * Force the enabled state of this change for a given package name. The change will only take
      * effect after that packages process is killed and restarted.
@@ -78,6 +101,7 @@
             mPackageOverrides = new HashMap<>();
         }
         mPackageOverrides.put(pname, enabled);
+        notifyListener(pname);
     }
 
     /**
@@ -89,7 +113,9 @@
      */
     void removePackageOverride(String pname) {
         if (mPackageOverrides != null) {
-            mPackageOverrides.remove(pname);
+            if (mPackageOverrides.remove(pname) != null) {
+                notifyListener(pname);
+            }
         }
     }
 
@@ -131,4 +157,10 @@
         }
         return sb.append(")").toString();
     }
+
+    private void notifyListener(String packageName) {
+        if (mListener != null) {
+            mListener.onCompatChange(packageName);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 490cce3..39c6e75 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -228,7 +228,7 @@
 
     /**
      * Removes all overrides previously added via {@link #addOverride(long, String, boolean)} or
-     * {@link #addAppOverrides(CompatibilityChangeConfig, String)} for a certain package.
+     * {@link #addOverrides(CompatibilityChangeConfig, String)} for a certain package.
      *
      * <p>This restores the default behaviour for the given change and app, once any app
      * processes have been restarted.
@@ -243,6 +243,27 @@
         }
     }
 
+    boolean registerListener(long changeId, CompatChange.ChangeListener listener) {
+        boolean alreadyKnown = true;
+        synchronized (mChanges) {
+            CompatChange c = mChanges.get(changeId);
+            if (c == null) {
+                alreadyKnown = false;
+                c = new CompatChange(changeId);
+                addChange(c);
+            }
+            c.registerListener(listener);
+        }
+        return alreadyKnown;
+    }
+
+    @VisibleForTesting
+    void clearChanges() {
+        synchronized (mChanges) {
+            mChanges.clear();
+        }
+    }
+
     /**
      * Dumps the current list of compatibility config information.
      *
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index ae5ad7e..4a3d7d6 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -107,6 +107,23 @@
         return enabled;
     }
 
+    /**
+     * Register a listener for change state overrides. Only one listener per change is allowed.
+     *
+     * <p>{@code listener.onCompatChange(String)} method is guaranteed to be called with
+     * packageName before the app is killed upon an override change. The state of a change is not
+     * guaranteed to change when {@code listener.onCompatChange(String)} is called.
+     *
+     * @param changeId to get updates for
+     * @param listener the listener that will be called upon a potential change for package.
+     * @throws IllegalStateException if a listener was already registered for changeId
+     * @returns {@code true} if a change with changeId was already known, or (@code false}
+     * otherwise.
+     */
+    public boolean registerListener(long changeId, CompatChange.ChangeListener listener) {
+        return CompatConfig.get().registerListener(changeId, listener);
+    }
+
     @Override
     public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
         CompatConfig.get().addOverrides(overrides, packageName);
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
new file mode 100644
index 0000000..c406876
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2019 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.server.compat;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
+import static org.testng.Assert.assertThrows;
+
+import android.compat.Compatibility;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import com.android.internal.compat.CompatibilityChangeConfig;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class PlatformCompatTest {
+    private static final String PACKAGE_NAME = "my.package";
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    CompatChange.ChangeListener mListener1, mListener2;
+
+
+    @Before
+    public void setUp() throws Exception {
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow(
+                new PackageManager.NameNotFoundException());
+        CompatConfig.get().clearChanges();
+    }
+
+    @Test
+    public void testRegisterListenerToSameIdThrows() {
+        PlatformCompat pc = new PlatformCompat(mContext);
+
+        // Registering a listener to change 1 is successful.
+        pc.registerListener(1, mListener1);
+        // Registering a listener to change 2 is successful.
+        pc.registerListener(2, mListener1);
+        // Trying to register another listener to change id 1 fails.
+        assertThrows(IllegalStateException.class, () -> pc.registerListener(1, mListener1));
+    }
+
+    @Test
+    public void testRegisterListenerReturn() {
+        PlatformCompat pc = new PlatformCompat(mContext);
+
+        pc.setOverrides(
+                new CompatibilityChangeConfig(
+                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())),
+                PACKAGE_NAME);
+
+        // Change id 1 is known (added in setOverrides).
+        assertThat(pc.registerListener(1, mListener1)).isTrue();
+        // Change 2 is unknown.
+        assertThat(pc.registerListener(2, mListener1)).isFalse();
+    }
+
+    @Test
+    public void testListenerCalledOnSetOverrides() {
+        PlatformCompat pc = new PlatformCompat(mContext);
+
+        pc.registerListener(1, mListener1);
+        pc.registerListener(2, mListener1);
+
+        pc.setOverrides(
+                new CompatibilityChangeConfig(
+                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+                PACKAGE_NAME);
+
+        verify(mListener1, times(2)).onCompatChange(PACKAGE_NAME);
+    }
+
+    @Test
+    public void testListenerNotCalledOnWrongPackage() {
+        PlatformCompat pc = new PlatformCompat(mContext);
+
+        pc.registerListener(1, mListener1);
+        pc.registerListener(2, mListener1);
+
+        pc.setOverridesForTest(
+                new CompatibilityChangeConfig(
+                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+                PACKAGE_NAME);
+
+        verify(mListener1, never()).onCompatChange("other.package");
+    }
+
+    @Test
+    public void testListenerCalledOnSetOverridesTwoListeners() {
+        PlatformCompat pc = new PlatformCompat(mContext);
+        pc.registerListener(1, mListener1);
+
+        final ImmutableSet<Long> enabled = ImmutableSet.of(1L);
+        final ImmutableSet<Long> disabled = ImmutableSet.of(2L);
+
+        pc.setOverrides(
+                new CompatibilityChangeConfig(
+                        new Compatibility.ChangeConfig(enabled, disabled)),
+                PACKAGE_NAME);
+
+        verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
+        verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
+
+        reset(mListener1);
+        reset(mListener2);
+
+        pc.registerListener(2, mListener2);
+
+        pc.setOverrides(
+                new CompatibilityChangeConfig(
+                        new Compatibility.ChangeConfig(enabled, disabled)),
+                PACKAGE_NAME);
+
+        verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
+        verify(mListener2, times(1)).onCompatChange(PACKAGE_NAME);
+    }
+
+    @Test
+    public void testListenerCalledOnSetOverridesForTest() {
+        PlatformCompat pc = new PlatformCompat(mContext);
+
+        pc.registerListener(1, mListener1);
+        pc.registerListener(2, mListener1);
+
+        pc.setOverridesForTest(
+                new CompatibilityChangeConfig(
+                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+                PACKAGE_NAME);
+
+        verify(mListener1, times(2)).onCompatChange(PACKAGE_NAME);
+    }
+
+    @Test
+    public void testListenerCalledOnSetOverridesTwoListenersForTest() {
+        PlatformCompat pc = new PlatformCompat(mContext);
+        pc.registerListener(1, mListener1);
+
+        final ImmutableSet<Long> enabled = ImmutableSet.of(1L);
+        final ImmutableSet<Long> disabled = ImmutableSet.of(2L);
+
+        pc.setOverridesForTest(
+                new CompatibilityChangeConfig(
+                        new Compatibility.ChangeConfig(enabled, disabled)),
+                PACKAGE_NAME);
+
+        verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
+        verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
+
+        reset(mListener1);
+        reset(mListener2);
+
+        pc.registerListener(2, mListener2);
+        pc.setOverridesForTest(
+                new CompatibilityChangeConfig(
+                        new Compatibility.ChangeConfig(enabled, disabled)),
+                PACKAGE_NAME);
+
+        verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
+        verify(mListener2, times(1)).onCompatChange(PACKAGE_NAME);
+    }
+
+    @Test
+    public void testListenerCalledOnClearOverrides() {
+        PlatformCompat pc = new PlatformCompat(mContext);
+
+        pc.registerListener(1, mListener1);
+        pc.registerListener(2, mListener2);
+
+        pc.setOverrides(
+                new CompatibilityChangeConfig(
+                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())),
+                PACKAGE_NAME);
+        verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
+        verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
+
+        reset(mListener1);
+        reset(mListener2);
+
+        pc.clearOverrides(PACKAGE_NAME);
+        verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
+        verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
+    }
+
+    @Test
+    public void testListenerCalledOnClearOverridesMultipleOverrides() {
+        PlatformCompat pc = new PlatformCompat(mContext);
+
+        pc.registerListener(1, mListener1);
+        pc.registerListener(2, mListener2);
+
+        pc.setOverrides(
+                new CompatibilityChangeConfig(
+                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+                PACKAGE_NAME);
+        verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
+        verify(mListener2, times(1)).onCompatChange(PACKAGE_NAME);
+
+        reset(mListener1);
+        reset(mListener2);
+
+        pc.clearOverrides(PACKAGE_NAME);
+        verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
+        verify(mListener2, times(1)).onCompatChange(PACKAGE_NAME);
+    }
+
+    @Test
+    public void testListenerCalledOnClearOverrideExists() {
+        PlatformCompat pc = new PlatformCompat(mContext);
+
+        pc.registerListener(1, mListener1);
+        pc.registerListener(2, mListener2);
+
+        pc.setOverrides(
+                new CompatibilityChangeConfig(
+                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())),
+                PACKAGE_NAME);
+        verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
+        verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
+
+        reset(mListener1);
+        reset(mListener2);
+
+        pc.clearOverride(1, PACKAGE_NAME);
+        verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
+        verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
+    }
+
+    @Test
+    public void testListenerCalledOnClearOverrideDoesntExist() {
+        PlatformCompat pc = new PlatformCompat(mContext);
+
+        pc.registerListener(1, mListener1);
+
+        pc.clearOverride(1, PACKAGE_NAME);
+        // Listener not called when a non existing override is removed.
+        verify(mListener1, never()).onCompatChange(PACKAGE_NAME);
+    }
+
+
+}