diff options
4 files changed, 344 insertions, 2 deletions
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index 87624359ef85..95582f73035a 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 @@ import java.util.Map; */ 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 @@ public final class CompatChange extends CompatibilityChangeInfo { 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 @@ public final class CompatChange extends CompatibilityChangeInfo { mPackageOverrides = new HashMap<>(); } mPackageOverrides.put(pname, enabled); + notifyListener(pname); } /** @@ -89,7 +113,9 @@ public final class CompatChange extends CompatibilityChangeInfo { */ void removePackageOverride(String pname) { if (mPackageOverrides != null) { - mPackageOverrides.remove(pname); + if (mPackageOverrides.remove(pname) != null) { + notifyListener(pname); + } } } @@ -131,4 +157,10 @@ public final class CompatChange extends CompatibilityChangeInfo { } 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 490cce347188..39c6e7552e3c 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 @@ final class CompatConfig { /** * 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 @@ final class CompatConfig { } } + 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 ae5ad7ea1261..4a3d7d69d874 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 @@ public class PlatformCompat extends IPlatformCompat.Stub { 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 000000000000..c406876c5cee --- /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); + } + + +} |