Add test target to f/b/packages/overlays.
- Added IconPackOverlayTest to ensure that there icon pack overlays do not
get out of sync with the underlying drawables in each apk.
- Remove overlayable_icons_test.xml from each app since this test makes
them no longer necessary
- Add apcts target OverlayTests.apk - TODO add this to presubmit config
for settings and systemui
Bug: 134566901
Test: atest IconPackOverlayTest
Change-Id: I99243db4839ac41dad01abf9f83ebda757dbbb77
diff --git a/packages/overlays/tests/Android.bp b/packages/overlays/tests/Android.bp
new file mode 100644
index 0000000..343367a
--- /dev/null
+++ b/packages/overlays/tests/Android.bp
@@ -0,0 +1,37 @@
+// 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.
+
+android_test {
+ name: "OverlayTests",
+
+ certificate: "platform",
+
+ srcs: ["src/**/*.java"],
+
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+
+ platform_apis: true,
+
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.espresso.core",
+ "mockito-target-minus-junit4",
+ "truth-prebuilt",
+ ],
+
+ dxflags: ["--multi-dex"],
+}
diff --git a/packages/overlays/tests/AndroidManifest.xml b/packages/overlays/tests/AndroidManifest.xml
new file mode 100644
index 0000000..6ebc555
--- /dev/null
+++ b/packages/overlays/tests/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.overlays">
+
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
+ <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY"/>
+ <uses-permission android:name="android.permission.SET_TIME_ZONE" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.systemui"
+ android:label="Tests for Overlays">
+ </instrumentation>
+</manifest>
diff --git a/packages/overlays/tests/AndroidTest.xml b/packages/overlays/tests/AndroidTest.xml
new file mode 100644
index 0000000..8843d62
--- /dev/null
+++ b/packages/overlays/tests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<configuration description="Runs Tests for Overlays.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="OverlayTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="OverlayTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.overlays" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/packages/overlays/tests/src/com/android/theme/icon/IconPackOverlayTest.java b/packages/overlays/tests/src/com/android/theme/icon/IconPackOverlayTest.java
new file mode 100644
index 0000000..6bc56ba
--- /dev/null
+++ b/packages/overlays/tests/src/com/android/theme/icon/IconPackOverlayTest.java
@@ -0,0 +1,293 @@
+/*
+ * 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.theme.icon;
+
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertEquals;
+
+import android.annotation.DrawableRes;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.text.TextUtils;
+import android.util.TypedValue;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.XmlUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class IconPackOverlayTest {
+ private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+ private static final String[] SYSTEMUI_ICON_PACK_OVERLAY_PACKAGES = {
+ "com.android.theme.icon_pack.circular.systemui",
+ "com.android.theme.icon_pack.rounded.systemui",
+ "com.android.theme.icon_pack.filled.systemui",
+ };
+ private static final String ANDROID_PACKAGE = "android";
+ private static final String[] ANDROID_ICON_PACK_OVERLAY_PACKAGES = {
+ "com.android.theme.icon_pack.circular.android",
+ "com.android.theme.icon_pack.rounded.android",
+ "com.android.theme.icon_pack.filled.android",
+ };
+ private static final String SETTINGS_PACKAGE = "com.android.settings";
+ private static final String[] SETTINGS_ICON_PACK_OVERLAY_PACKAGES = {
+ "com.android.theme.icon_pack.circular.settings",
+ "com.android.theme.icon_pack.rounded.settings",
+ "com.android.theme.icon_pack.filled.settings",
+ };
+
+ private static final int[] VECTOR_ATTRIBUTES = {
+ android.R.attr.tint,
+ android.R.attr.height,
+ android.R.attr.width,
+ android.R.attr.alpha,
+ android.R.attr.autoMirrored,
+ };
+
+ private final TypedValue mTargetTypedValue = new TypedValue();
+ private final TypedValue mOverlayTypedValue = new TypedValue();
+ private Context mContext;
+
+ @Before
+ public void setup() {
+ mContext = InstrumentationRegistry.getContext();
+ }
+
+ /**
+ * Ensure that drawable icons in icon packs targeting android have corresponding underlying
+ * drawables in android. This test fails if you remove/rename an overlaid icon in android.
+ * If so, make the same change to the corresponding drawables in the overlay packages.
+ */
+ @Test
+ public void testAndroidFramework_containsAllOverlayedIcons() {
+ containsAllOverlayedIcons(ANDROID_PACKAGE, ANDROID_ICON_PACK_OVERLAY_PACKAGES);
+ }
+
+ /**
+ * Ensure that drawable icons in icon packs targeting settings have corresponding underlying
+ * drawables in settings. This test fails if you remove/rename an overlaid icon in settings.
+ * If so, make the same change to the corresponding drawables in the overlay packages.
+ */
+ @Test
+ public void testSettings_containsAllOverlayedIcons() {
+ containsAllOverlayedIcons(SETTINGS_PACKAGE, SETTINGS_ICON_PACK_OVERLAY_PACKAGES);
+ }
+
+ /**
+ * Ensure that drawable icons in icon packs targeting systemui have corresponding underlying
+ * drawables in systemui. This test fails if you remove/rename an overlaid icon in systemui.
+ * If so, make the same change to the corresponding drawables in the overlay packages.
+ */
+ @Test
+ public void testSystemUI_containAllOverlayedIcons() {
+ containsAllOverlayedIcons(SYSTEMUI_PACKAGE, SYSTEMUI_ICON_PACK_OVERLAY_PACKAGES);
+ }
+
+ /**
+ * Ensures that all overlay icons have the same values for {@link #VECTOR_ATTRIBUTES} as the
+ * underlying drawable in android. To fix this test, make the attribute change to all of the
+ * corresponding drawables in the overlay packages.
+ */
+ @Test
+ public void testAndroidFramework_hasEqualVectorDrawableAttributes() {
+ hasEqualVectorDrawableAttributes(ANDROID_PACKAGE, ANDROID_ICON_PACK_OVERLAY_PACKAGES);
+ }
+
+ /**
+ * Ensures that all overlay icons have the same values for {@link #VECTOR_ATTRIBUTES} as the
+ * underlying drawable in settings. To fix this test, make the attribute change to all of the
+ * corresponding drawables in the overlay packages.
+ */
+ @Test
+ public void testSettings_hasEqualVectorDrawableAttributes() {
+ hasEqualVectorDrawableAttributes(SETTINGS_PACKAGE, SETTINGS_ICON_PACK_OVERLAY_PACKAGES);
+ }
+
+ /**
+ * Ensures that all overlay icons have the same values for {@link #VECTOR_ATTRIBUTES} as the
+ * underlying drawable in systemui. To fix this test, make the attribute change to all of the
+ * corresponding drawables in the overlay packages.
+ */
+ @Test
+ public void testSystemUI_hasEqualVectorDrawableAttributes() {
+ hasEqualVectorDrawableAttributes(SYSTEMUI_PACKAGE, SYSTEMUI_ICON_PACK_OVERLAY_PACKAGES);
+ }
+
+ private void containsAllOverlayedIcons(String targetPkg, String[] overlayPkgs) {
+ final Resources targetResources;
+ try {
+ targetResources = mContext.getPackageManager()
+ .getResourcesForApplication(targetPkg);
+ } catch (PackageManager.NameNotFoundException e) {
+ return; // No need to test overlays if target package does not exist on the system.
+ }
+
+ StringBuilder errors = new StringBuilder();
+ for (String overlayPackage : overlayPkgs) {
+ final ApplicationInfo info;
+ try {
+ info = mContext.getPackageManager().getApplicationInfo(overlayPackage, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ continue; // No need to test overlay resources if apk is not on the system.
+ }
+ final List<String> iconPackDrawables = getDrawablesFromOverlay(info);
+ for (int i = 0; i < iconPackDrawables.size(); i++) {
+ String resourceName = iconPackDrawables.get(i);
+ int targetRid = targetResources.getIdentifier(resourceName, "drawable", targetPkg);
+ if (targetRid == Resources.ID_NULL) {
+ errors.append(String.format("[%s] is not contained in the target package [%s]",
+ resourceName, targetPkg));
+ }
+ }
+ }
+
+ if (!TextUtils.isEmpty(errors)) {
+ fail(errors.toString());
+ }
+ }
+
+ private void hasEqualVectorDrawableAttributes(String targetPkg, String[] overlayPackages) {
+ final Resources targetRes;
+ try {
+ targetRes = mContext.getPackageManager().getResourcesForApplication(targetPkg);
+ } catch (PackageManager.NameNotFoundException e) {
+ return; // No need to test overlays if target package does not exist on the system.
+ }
+
+ StringBuilder errors = new StringBuilder();
+
+ for (String overlayPkg : overlayPackages) {
+ final ApplicationInfo info;
+ try {
+ info = mContext.getPackageManager().getApplicationInfo(overlayPkg, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ continue; // No need to test overlay resources if apk is not on the system.
+ }
+ final List<String> iconPackDrawables = getDrawablesFromOverlay(info);
+ final Resources overlayRes;
+ try {
+ overlayRes = mContext.getPackageManager().getResourcesForApplication(overlayPkg);
+ } catch (PackageManager.NameNotFoundException e) {
+ continue; // No need to test overlay resources if apk is not on the system.
+ }
+
+ for (int i = 0; i < iconPackDrawables.size(); i++) {
+ String resourceName = iconPackDrawables.get(i);
+ int targetRid = targetRes.getIdentifier(resourceName, "drawable", targetPkg);
+ int overlayRid = overlayRes.getIdentifier(resourceName, "drawable", overlayPkg);
+ TypedArray targetAttrs = getAVDAttributes(targetRes, targetRid);
+ if (targetAttrs == null) {
+ errors.append(String.format(
+ "[%s] in pkg [%s] does not exist or is not a valid vector drawable.\n",
+ resourceName, targetPkg));
+ continue;
+ }
+
+ TypedArray overlayAttrs = getAVDAttributes(overlayRes, overlayRid);
+ if (overlayAttrs == null) {
+ errors.append(String.format(
+ "[%s] in pkg [%s] does not exist or is not a valid vector drawable.\n",
+ resourceName, overlayPkg));
+ continue;
+ }
+
+ if (!attributesEquals(targetAttrs, overlayAttrs)) {
+ errors.append(String.format("[drawable/%s] in [%s] does not have the same "
+ + "attributes as the corresponding drawable from [%s]\n",
+ resourceName, targetPkg, overlayPkg));
+ }
+ targetAttrs.recycle();
+ overlayAttrs.recycle();
+ }
+ }
+
+ if (!TextUtils.isEmpty(errors)) {
+ fail(errors.toString());
+ }
+ }
+
+ private TypedArray getAVDAttributes(Resources resources, @DrawableRes int rid) {
+ try {
+ XmlResourceParser parser = resources.getXml(rid);
+ XmlUtils.nextElement(parser);
+ // Always use the the test apk theme to resolve attributes.
+ return mContext.getTheme().obtainStyledAttributes(parser, VECTOR_ATTRIBUTES, 0, 0);
+ } catch (XmlPullParserException | IOException | Resources.NotFoundException e) {
+ return null;
+ }
+ }
+
+ private boolean attributesEquals(TypedArray target, TypedArray overlay) {
+ assertEquals(target.length(), overlay.length());
+ for (int i = 0; i < target.length(); i++) {
+ target.getValue(i, mTargetTypedValue);
+ overlay.getValue(i, mOverlayTypedValue);
+ if (!attributesEquals(mTargetTypedValue, mOverlayTypedValue)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean attributesEquals(TypedValue target, TypedValue overlay) {
+ return target.type == overlay.type && target.data == overlay.data;
+ }
+
+ private static List<String> getDrawablesFromOverlay(ApplicationInfo applicationInfo) {
+ try {
+ final ArrayList<String> drawables = new ArrayList<>();
+ ZipFile file = new ZipFile(applicationInfo.sourceDir);
+ Enumeration<? extends ZipEntry> entries = file.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry element = entries.nextElement();
+ String name = element.getName();
+ if (name.contains("/drawable/")) {
+ name = name.substring(name.lastIndexOf('/') + 1);
+ if (name.contains(".")) {
+ name = name.substring(0, name.indexOf('.'));
+ }
+ drawables.add(name);
+ }
+ }
+ return drawables;
+ } catch (IOException e) {
+ fail(String.format("Failed to retrieve drawables from package [%s] with message [%s]",
+ applicationInfo.packageName, e.getMessage()));
+ return null;
+ }
+ }
+}