Loading services/core/java/com/android/server/wm/SplashScreenExceptionList.java 0 → 100644 +127 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.wm; import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ApplicationInfo; import android.os.Build; import android.provider.DeviceConfig; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.HashSet; import java.util.Locale; import java.util.concurrent.Executor; import java.util.function.Supplier; /** * Handles filtering the list of package that don't use the system splash screen. * The list is backed by a {@link DeviceConfig} property. * <p> * An application can manually opt-out of the exception list by setting the <meta-data * {@value OPT_OUT_METADATA_FLAG}="true"/> in the <code><application></code> section of the * manifest. */ class SplashScreenExceptionList { private static final boolean DEBUG = Build.isDebuggable(); private static final String LOG_TAG = "SplashScreenExceptionList"; private static final String KEY_SPLASH_SCREEN_EXCEPTION_LIST = "splash_screen_exception_list"; private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER; private static final String OPT_OUT_METADATA_FLAG = "android.splashscreen.exception_opt_out"; @GuardedBy("mLock") private final HashSet<String> mDeviceConfigExcludedPackages = new HashSet<>(); private final Object mLock = new Object(); @VisibleForTesting final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener; SplashScreenExceptionList(@NonNull Executor executor) { updateDeviceConfig(DeviceConfig.getString(NAMESPACE, KEY_SPLASH_SCREEN_EXCEPTION_LIST, "")); mOnPropertiesChangedListener = properties -> updateDeviceConfig( properties.getString(KEY_SPLASH_SCREEN_EXCEPTION_LIST, "")); DeviceConfig.addOnPropertiesChangedListener(NAMESPACE, executor, mOnPropertiesChangedListener); } private void updateDeviceConfig(String values) { parseDeviceConfigPackageList(values); } /** * Returns true if the packageName is in the list and the target sdk is before S. * * @param packageName The package name of the application to check * @param targetSdk The target sdk of the application * @param infoSupplier A {@link Supplier} that returns an {@link ApplicationInfo} used to * check if the application wants to opt-out of the exception list in the * manifest metadata. Evaluated only if the application is in the exception * list. */ @SuppressWarnings("AndroidFrameworkCompatChange") // Target sdk check public boolean isException(@NonNull String packageName, int targetSdk, @Nullable Supplier<ApplicationInfo> infoSupplier) { if (targetSdk >= Build.VERSION_CODES.S) { return false; } synchronized (mLock) { if (DEBUG) { Slog.v(LOG_TAG, String.format(Locale.US, "SplashScreen checking exception for package %s (target sdk:%d) -> %s", packageName, targetSdk, mDeviceConfigExcludedPackages.contains(packageName))); } if (!mDeviceConfigExcludedPackages.contains(packageName)) { return false; } } return !isOptedOut(infoSupplier); } /** * An application can manually opt-out of the exception list by setting the meta-data * {@value OPT_OUT_METADATA_FLAG} = true in the <code>application</code> section of the manifest */ private static boolean isOptedOut(@Nullable Supplier<ApplicationInfo> infoProvider) { if (infoProvider == null) { return false; } ApplicationInfo info = infoProvider.get(); return info != null && info.metaData != null && info.metaData.getBoolean( OPT_OUT_METADATA_FLAG, false); } private void parseDeviceConfigPackageList(String rawList) { synchronized (mLock) { mDeviceConfigExcludedPackages.clear(); String[] packages = rawList.split(","); for (String packageName : packages) { String packageNameTrimmed = packageName.trim(); if (!packageNameTrimmed.isEmpty()) { mDeviceConfigExcludedPackages.add(packageNameTrimmed); } } } } } services/core/java/com/android/server/wm/StartingSurfaceController.java +15 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,9 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_EMPTY_SPLASH_ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.os.SystemProperties; Loading @@ -34,6 +37,8 @@ import android.window.TaskSnapshot; import com.android.server.policy.WindowManagerPolicy.StartingSurface; import java.util.function.Supplier; /** * Managing to create and release a starting window surface. */ Loading @@ -44,9 +49,11 @@ public class StartingSurfaceController { static final boolean DEBUG_ENABLE_SHELL_DRAWER = SystemProperties.getBoolean("persist.debug.shell_starting_surface", true); private final WindowManagerService mService; private final SplashScreenExceptionList mSplashScreenExceptionsList; public StartingSurfaceController(WindowManagerService wm) { mService = wm; mSplashScreenExceptionsList = new SplashScreenExceptionList(wm.mContext.getMainExecutor()); } StartingSurface createSplashScreenStartingSurface(ActivityRecord activity, String packageName, Loading @@ -68,6 +75,14 @@ public class StartingSurfaceController { return null; } /** * @see SplashScreenExceptionList#isException(String, int, Supplier) */ boolean isExceptionApp(@NonNull String packageName, int targetSdk, @Nullable Supplier<ApplicationInfo> infoProvider) { return mSplashScreenExceptionsList.isException(packageName, targetSdk, infoProvider); } int makeStartingWindowTypeParameter(boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated, boolean useEmpty) { Loading services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java 0 → 100644 +151 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.wm; import static android.os.Build.VERSION_CODES; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.pm.ApplicationInfo; import android.os.Bundle; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import androidx.test.filters.MediumTest; import junit.framework.Assert; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Test for the splash screen exception list * atest WmTests:SplashScreenExceptionListTest */ @MediumTest @Presubmit public class SplashScreenExceptionListTest { // Constant copied on purpose so it's not refactored by accident. // If the key needs to be modified, the server side key also needs to be changed. private static final String KEY_SPLASH_SCREEN_EXCEPTION_LIST = "splash_screen_exception_list"; private DeviceConfig.Properties mInitialWindowManagerProperties; private final HandlerExecutor mExecutor = new HandlerExecutor( new Handler(Looper.getMainLooper())); private final SplashScreenExceptionList mList = new SplashScreenExceptionList(mExecutor); @Before public void setUp() throws Exception { mInitialWindowManagerProperties = DeviceConfig.getProperties( DeviceConfig.NAMESPACE_WINDOW_MANAGER); clearConstrainDisplayApisFlags(); } private void clearConstrainDisplayApisFlags() { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_SPLASH_SCREEN_EXCEPTION_LIST, null, /* makeDefault= */ false); } @After public void tearDown() throws Exception { DeviceConfig.setProperties(mInitialWindowManagerProperties); DeviceConfig.removeOnPropertiesChangedListener(mList.mOnPropertiesChangedListener); } @Test public void packageFromDeviceConfigIgnored() { setExceptionListAndWaitForCallback("com.test.nosplashscreen1,com.test.nosplashscreen2"); assertIsException("com.test.nosplashscreen1", null); assertIsException("com.test.nosplashscreen2", null); assertIsNotException("com.test.nosplashscreen1", VERSION_CODES.S, null); assertIsNotException("com.test.nosplashscreen2", VERSION_CODES.S, null); assertIsNotException("com.test.splashscreen", VERSION_CODES.S, null); assertIsNotException("com.test.splashscreen", VERSION_CODES.R, null); } private void setExceptionListAndWaitForCallback(String commaSeparatedList) { CountDownLatch latch = new CountDownLatch(1); DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener = (p) -> { if (commaSeparatedList.equals(p.getString(KEY_SPLASH_SCREEN_EXCEPTION_LIST, null))) { latch.countDown(); } }; DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER, mExecutor, onPropertiesChangedListener); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_SPLASH_SCREEN_EXCEPTION_LIST, commaSeparatedList, false); try { assertTrue("Timed out waiting for DeviceConfig to be updated.", latch.await(1, TimeUnit.SECONDS)); } catch (InterruptedException e) { Assert.fail(e.getMessage()); } finally { DeviceConfig.removeOnPropertiesChangedListener(onPropertiesChangedListener); } } @Test public void metaDataOptOut() { String packageName = "com.test.nosplashscreen_opt_out"; setExceptionListAndWaitForCallback(packageName); Bundle metaData = new Bundle(); ApplicationInfo activityInfo = new ApplicationInfo(); activityInfo.metaData = metaData; // No Exceptions metaData.putBoolean("android.splashscreen.exception_opt_out", true); assertIsNotException(packageName, VERSION_CODES.R, activityInfo); assertIsNotException(packageName, VERSION_CODES.S, activityInfo); // Exception Pre S metaData.putBoolean("android.splashscreen.exception_opt_out", false); assertIsException(packageName, activityInfo); assertIsNotException(packageName, VERSION_CODES.S, activityInfo); // Edge Cases activityInfo.metaData = null; assertIsException(packageName, activityInfo); assertIsException(packageName, null); } private void assertIsNotException(String packageName, int targetSdk, ApplicationInfo activityInfo) { assertFalse(String.format("%s (sdk=%d) should have not been considered as an exception", packageName, targetSdk), mList.isException(packageName, targetSdk, () -> activityInfo)); } private void assertIsException(String packageName, ApplicationInfo activityInfo) { assertTrue(String.format("%s (sdk=%d) should have been considered as an exception", packageName, VERSION_CODES.R), mList.isException(packageName, VERSION_CODES.R, () -> activityInfo)); } } Loading
services/core/java/com/android/server/wm/SplashScreenExceptionList.java 0 → 100644 +127 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.wm; import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ApplicationInfo; import android.os.Build; import android.provider.DeviceConfig; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.HashSet; import java.util.Locale; import java.util.concurrent.Executor; import java.util.function.Supplier; /** * Handles filtering the list of package that don't use the system splash screen. * The list is backed by a {@link DeviceConfig} property. * <p> * An application can manually opt-out of the exception list by setting the <meta-data * {@value OPT_OUT_METADATA_FLAG}="true"/> in the <code><application></code> section of the * manifest. */ class SplashScreenExceptionList { private static final boolean DEBUG = Build.isDebuggable(); private static final String LOG_TAG = "SplashScreenExceptionList"; private static final String KEY_SPLASH_SCREEN_EXCEPTION_LIST = "splash_screen_exception_list"; private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER; private static final String OPT_OUT_METADATA_FLAG = "android.splashscreen.exception_opt_out"; @GuardedBy("mLock") private final HashSet<String> mDeviceConfigExcludedPackages = new HashSet<>(); private final Object mLock = new Object(); @VisibleForTesting final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener; SplashScreenExceptionList(@NonNull Executor executor) { updateDeviceConfig(DeviceConfig.getString(NAMESPACE, KEY_SPLASH_SCREEN_EXCEPTION_LIST, "")); mOnPropertiesChangedListener = properties -> updateDeviceConfig( properties.getString(KEY_SPLASH_SCREEN_EXCEPTION_LIST, "")); DeviceConfig.addOnPropertiesChangedListener(NAMESPACE, executor, mOnPropertiesChangedListener); } private void updateDeviceConfig(String values) { parseDeviceConfigPackageList(values); } /** * Returns true if the packageName is in the list and the target sdk is before S. * * @param packageName The package name of the application to check * @param targetSdk The target sdk of the application * @param infoSupplier A {@link Supplier} that returns an {@link ApplicationInfo} used to * check if the application wants to opt-out of the exception list in the * manifest metadata. Evaluated only if the application is in the exception * list. */ @SuppressWarnings("AndroidFrameworkCompatChange") // Target sdk check public boolean isException(@NonNull String packageName, int targetSdk, @Nullable Supplier<ApplicationInfo> infoSupplier) { if (targetSdk >= Build.VERSION_CODES.S) { return false; } synchronized (mLock) { if (DEBUG) { Slog.v(LOG_TAG, String.format(Locale.US, "SplashScreen checking exception for package %s (target sdk:%d) -> %s", packageName, targetSdk, mDeviceConfigExcludedPackages.contains(packageName))); } if (!mDeviceConfigExcludedPackages.contains(packageName)) { return false; } } return !isOptedOut(infoSupplier); } /** * An application can manually opt-out of the exception list by setting the meta-data * {@value OPT_OUT_METADATA_FLAG} = true in the <code>application</code> section of the manifest */ private static boolean isOptedOut(@Nullable Supplier<ApplicationInfo> infoProvider) { if (infoProvider == null) { return false; } ApplicationInfo info = infoProvider.get(); return info != null && info.metaData != null && info.metaData.getBoolean( OPT_OUT_METADATA_FLAG, false); } private void parseDeviceConfigPackageList(String rawList) { synchronized (mLock) { mDeviceConfigExcludedPackages.clear(); String[] packages = rawList.split(","); for (String packageName : packages) { String packageNameTrimmed = packageName.trim(); if (!packageNameTrimmed.isEmpty()) { mDeviceConfigExcludedPackages.add(packageNameTrimmed); } } } } }
services/core/java/com/android/server/wm/StartingSurfaceController.java +15 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,9 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_EMPTY_SPLASH_ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.os.SystemProperties; Loading @@ -34,6 +37,8 @@ import android.window.TaskSnapshot; import com.android.server.policy.WindowManagerPolicy.StartingSurface; import java.util.function.Supplier; /** * Managing to create and release a starting window surface. */ Loading @@ -44,9 +49,11 @@ public class StartingSurfaceController { static final boolean DEBUG_ENABLE_SHELL_DRAWER = SystemProperties.getBoolean("persist.debug.shell_starting_surface", true); private final WindowManagerService mService; private final SplashScreenExceptionList mSplashScreenExceptionsList; public StartingSurfaceController(WindowManagerService wm) { mService = wm; mSplashScreenExceptionsList = new SplashScreenExceptionList(wm.mContext.getMainExecutor()); } StartingSurface createSplashScreenStartingSurface(ActivityRecord activity, String packageName, Loading @@ -68,6 +75,14 @@ public class StartingSurfaceController { return null; } /** * @see SplashScreenExceptionList#isException(String, int, Supplier) */ boolean isExceptionApp(@NonNull String packageName, int targetSdk, @Nullable Supplier<ApplicationInfo> infoProvider) { return mSplashScreenExceptionsList.isException(packageName, targetSdk, infoProvider); } int makeStartingWindowTypeParameter(boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated, boolean useEmpty) { Loading
services/tests/wmtests/src/com/android/server/wm/SplashScreenExceptionListTest.java 0 → 100644 +151 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.wm; import static android.os.Build.VERSION_CODES; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.pm.ApplicationInfo; import android.os.Bundle; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import androidx.test.filters.MediumTest; import junit.framework.Assert; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Test for the splash screen exception list * atest WmTests:SplashScreenExceptionListTest */ @MediumTest @Presubmit public class SplashScreenExceptionListTest { // Constant copied on purpose so it's not refactored by accident. // If the key needs to be modified, the server side key also needs to be changed. private static final String KEY_SPLASH_SCREEN_EXCEPTION_LIST = "splash_screen_exception_list"; private DeviceConfig.Properties mInitialWindowManagerProperties; private final HandlerExecutor mExecutor = new HandlerExecutor( new Handler(Looper.getMainLooper())); private final SplashScreenExceptionList mList = new SplashScreenExceptionList(mExecutor); @Before public void setUp() throws Exception { mInitialWindowManagerProperties = DeviceConfig.getProperties( DeviceConfig.NAMESPACE_WINDOW_MANAGER); clearConstrainDisplayApisFlags(); } private void clearConstrainDisplayApisFlags() { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_SPLASH_SCREEN_EXCEPTION_LIST, null, /* makeDefault= */ false); } @After public void tearDown() throws Exception { DeviceConfig.setProperties(mInitialWindowManagerProperties); DeviceConfig.removeOnPropertiesChangedListener(mList.mOnPropertiesChangedListener); } @Test public void packageFromDeviceConfigIgnored() { setExceptionListAndWaitForCallback("com.test.nosplashscreen1,com.test.nosplashscreen2"); assertIsException("com.test.nosplashscreen1", null); assertIsException("com.test.nosplashscreen2", null); assertIsNotException("com.test.nosplashscreen1", VERSION_CODES.S, null); assertIsNotException("com.test.nosplashscreen2", VERSION_CODES.S, null); assertIsNotException("com.test.splashscreen", VERSION_CODES.S, null); assertIsNotException("com.test.splashscreen", VERSION_CODES.R, null); } private void setExceptionListAndWaitForCallback(String commaSeparatedList) { CountDownLatch latch = new CountDownLatch(1); DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener = (p) -> { if (commaSeparatedList.equals(p.getString(KEY_SPLASH_SCREEN_EXCEPTION_LIST, null))) { latch.countDown(); } }; DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER, mExecutor, onPropertiesChangedListener); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_SPLASH_SCREEN_EXCEPTION_LIST, commaSeparatedList, false); try { assertTrue("Timed out waiting for DeviceConfig to be updated.", latch.await(1, TimeUnit.SECONDS)); } catch (InterruptedException e) { Assert.fail(e.getMessage()); } finally { DeviceConfig.removeOnPropertiesChangedListener(onPropertiesChangedListener); } } @Test public void metaDataOptOut() { String packageName = "com.test.nosplashscreen_opt_out"; setExceptionListAndWaitForCallback(packageName); Bundle metaData = new Bundle(); ApplicationInfo activityInfo = new ApplicationInfo(); activityInfo.metaData = metaData; // No Exceptions metaData.putBoolean("android.splashscreen.exception_opt_out", true); assertIsNotException(packageName, VERSION_CODES.R, activityInfo); assertIsNotException(packageName, VERSION_CODES.S, activityInfo); // Exception Pre S metaData.putBoolean("android.splashscreen.exception_opt_out", false); assertIsException(packageName, activityInfo); assertIsNotException(packageName, VERSION_CODES.S, activityInfo); // Edge Cases activityInfo.metaData = null; assertIsException(packageName, activityInfo); assertIsException(packageName, null); } private void assertIsNotException(String packageName, int targetSdk, ApplicationInfo activityInfo) { assertFalse(String.format("%s (sdk=%d) should have not been considered as an exception", packageName, targetSdk), mList.isException(packageName, targetSdk, () -> activityInfo)); } private void assertIsException(String packageName, ApplicationInfo activityInfo) { assertTrue(String.format("%s (sdk=%d) should have been considered as an exception", packageName, VERSION_CODES.R), mList.isException(packageName, VERSION_CODES.R, () -> activityInfo)); } }