diff options
80 files changed, 729 insertions, 233 deletions
diff --git a/.idea/misc.xml b/.idea/misc.xml index 0a1e4e0324..d47ec03f54 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -53,7 +53,7 @@ </value> </option> </component> - <component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> + <component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/out" /> </component> </project>
\ No newline at end of file diff --git a/Android.bp b/Android.bp index 76bc916b23..5fd342d124 100644 --- a/Android.bp +++ b/Android.bp @@ -30,7 +30,7 @@ java_genrule_host { tools: ["layoutlib_create"], out: ["temp_layoutlib.jar"], srcs: [ - ":atf-prebuilt-502584086{.jar}", + ":atf-prebuilt-557133692{.jar}", ":core-icu4j-for-host{.jar}", ":core-libart-for-host{.jar}", ":framework-all{.jar}", diff --git a/bridge/src/android/content/res/BridgeTypedArray.java b/bridge/src/android/content/res/BridgeTypedArray.java index da59fb79a6..ae6f538178 100644 --- a/bridge/src/android/content/res/BridgeTypedArray.java +++ b/bridge/src/android/content/res/BridgeTypedArray.java @@ -907,7 +907,7 @@ public final class BridgeTypedArray extends TypedArray { boolean found = false; String value = mResourceData[index].getValue(); - if (!value.isEmpty()) { + if (value != null && !value.isEmpty()) { // Check if the value string is already representing an integer and return it if so. // Resources coming from res.apk in an AAR may have flags and enums in integer form. char c = value.charAt(0); diff --git a/bridge/src/android/content/res/Resources_Delegate.java b/bridge/src/android/content/res/Resources_Delegate.java index 7aa02f9464..4eb6bddeed 100644 --- a/bridge/src/android/content/res/Resources_Delegate.java +++ b/bridge/src/android/content/res/Resources_Delegate.java @@ -87,6 +87,8 @@ public class Resources_Delegate { "Resources_Delegate.initSystem called twice before disposeSystem was called"; Resources resources = new Resources(Resources_Delegate.class.getClassLoader()); resources.setImpl(new ResourcesImpl(assets, metrics, config, new DisplayAdjustments())); + resources.getConfiguration().windowConfiguration.setMaxBounds(0, 0, metrics.widthPixels, + metrics.heightPixels); sContexts.put(resources, Objects.requireNonNull(context)); sLayoutlibCallbacks.put(resources, Objects.requireNonNull(layoutlibCallback)); return Resources.mSystem = resources; diff --git a/bridge/src/android/os/NullVibratorManager.java b/bridge/src/android/os/NullVibratorManager.java new file mode 100644 index 0000000000..fe269c7d66 --- /dev/null +++ b/bridge/src/android/os/NullVibratorManager.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 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 android.os; + +public class NullVibratorManager extends VibratorManager { + private static final NullVibratorManager sInstance = new NullVibratorManager(); + + public static NullVibratorManager getInstance() { + return sInstance; + } + + private NullVibratorManager() { } + + @Override + public int[] getVibratorIds() { + return new int[0]; + } + + @Override + public Vibrator getVibrator(int vibratorId) { + return NullVibrator.getInstance(); + } + + @Override + public Vibrator getDefaultVibrator() { + return NullVibrator.getInstance(); + } + + @Override + public void vibrate(int uid, String opPkg, CombinedVibration effect, String reason, + VibrationAttributes attributes) { } + + @Override + public void cancel() { } + + @Override + public void cancel(int usageFilter) { } +} diff --git a/bridge/src/android/util/BridgeXmlPullAttributes.java b/bridge/src/android/util/BridgeXmlPullAttributes.java index 8a78fa3843..07bc437b12 100644 --- a/bridge/src/android/util/BridgeXmlPullAttributes.java +++ b/bridge/src/android/util/BridgeXmlPullAttributes.java @@ -151,6 +151,9 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes implements Resolv @Override public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) { String value = getAttributeValue(namespace, attribute); + if (value == null) { + return defaultValue; + } return resolveResourceValue(value, defaultValue); } diff --git a/bridge/src/android/view/AttachInfo_Accessor.java b/bridge/src/android/view/AttachInfo_Accessor.java index cd35899c43..2e38b31793 100644 --- a/bridge/src/android/view/AttachInfo_Accessor.java +++ b/bridge/src/android/view/AttachInfo_Accessor.java @@ -46,7 +46,11 @@ public class AttachInfo_Accessor { public static void detachFromWindow(final View view) { if (view != null) { + final View.AttachInfo attachInfo = view.mAttachInfo; view.dispatchDetachedFromWindow(); + if (attachInfo != null) { + ViewRootImpl_Accessor.detachFromWindow(attachInfo.mViewRootImpl); + } } } diff --git a/bridge/src/android/view/ViewRootImpl_Accessor.java b/bridge/src/android/view/ViewRootImpl_Accessor.java index 81ffe2e324..691f59ae46 100644 --- a/bridge/src/android/view/ViewRootImpl_Accessor.java +++ b/bridge/src/android/view/ViewRootImpl_Accessor.java @@ -30,4 +30,9 @@ public class ViewRootImpl_Accessor { viewRoot.mWidth = child.getWidth(); viewRoot.mHeight = child.getHeight(); } + + public static void detachFromWindow(ViewRootImpl viewRoot) { + viewRoot.mAccessibilityInteractionConnectionManager.ensureNoConnection(); + viewRoot.mAccessibilityInteractionConnectionManager.ensureNoDirectConnection(); + } } diff --git a/bridge/src/android/view/accessibility/AccessibilityManager_Delegate.java b/bridge/src/android/view/accessibility/AccessibilityManager_Delegate.java new file mode 100644 index 0000000000..9408f79c66 --- /dev/null +++ b/bridge/src/android/view/accessibility/AccessibilityManager_Delegate.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 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 android.view.accessibility; + +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.content.Context; +import android.graphics.Matrix; +import android.view.MagnificationSpec; +import android.view.accessibility.IAccessibilityManager.WindowTransformationSpec; + +public class AccessibilityManager_Delegate { + private static WindowTransformationSpec sInstance; + + @LayoutlibDelegate + public static IAccessibilityManager.WindowTransformationSpec getWindowTransformationSpec( + AccessibilityManager thisManager, int windowId) { + if (sInstance == null) { + WindowTransformationSpec spec = new WindowTransformationSpec(); + spec.magnificationSpec = new MagnificationSpec(); + float[] matrix = new float[9]; + Matrix.IDENTITY_MATRIX.getValues(matrix); + spec.transformationMatrix = matrix; + sInstance = spec; + } + return sInstance; + } + + @LayoutlibDelegate + public static AccessibilityManager getInstance(Context context) { + Context baseContext = BridgeContext.getBaseContext(context); + return ((BridgeContext)baseContext).getAccessibilityManager(); + } +} diff --git a/bridge/src/com/android/layoutlib/bridge/Bridge.java b/bridge/src/com/android/layoutlib/bridge/Bridge.java index 3c62193dad..ce0fb2ddd8 100644 --- a/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -337,7 +337,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { } /** - * Tests if the field is pubic, static and one of int or int[]. + * Tests if the field is public, static and one of int or int[]. */ private static boolean isValidRField(Field field) { int modifiers = field.getModifiers(); diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index a0cf2b23b2..0efe9a0c86 100644 --- a/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -40,6 +40,7 @@ import com.android.tools.layoutlib.annotations.NotNull; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.animation.AnimationHandler; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -81,6 +82,8 @@ import android.os.Handler; import android.os.IBinder; import android.os.IInterface; import android.os.Looper; +import android.os.NullVibrator; +import android.os.NullVibratorManager; import android.os.Parcel; import android.os.PowerManager; import android.os.RemoteException; @@ -199,10 +202,12 @@ public class BridgeContext extends Context { private Boolean mIsThemeAppCompat; private boolean mUseThemedIcon; private Context mApplicationContext; + private AccessibilityManager mAccessibilityManager; private final ResourceNamespace mAppCompatNamespace; private final Map<Key<?>, Object> mUserData = new HashMap<>(); private final SessionInteractiveData mSessionInteractiveData; + private final ThreadLocal<AnimationHandler> mAnimationHandlerThreadLocal = new ThreadLocal<>(); /** * Some applications that target both pre API 17 and post API 17, set the newer attrs to @@ -462,9 +467,12 @@ public class BridgeContext extends Context { try { outValue.data = Integer.parseInt(stringValue); outValue.type = TypedValue.TYPE_INT_DEC; - } catch (NumberFormatException e) { - outValue.type = TypedValue.TYPE_STRING; - outValue.string = stringValue; + } + catch (NumberFormatException e) { + if (!ResourceHelper.parseFloatAttribute(null, stringValue, outValue, false)) { + outValue.type = TypedValue.TYPE_STRING; + outValue.string = stringValue; + } } } } @@ -601,6 +609,13 @@ public class BridgeContext extends Context { return isThemeAppCompat; } + public AccessibilityManager getAccessibilityManager() { + if (mAccessibilityManager == null) { + mAccessibilityManager = new AccessibilityManager(this, null, UserHandle.USER_CURRENT); + } + return mAccessibilityManager; + } + // ------------ Context methods @Override @@ -690,6 +705,12 @@ public class BridgeContext extends Context { case INPUT_SERVICE: return mInputManager; + case VIBRATOR_SERVICE: + return NullVibrator.getInstance(); + + case VIBRATOR_MANAGER_SERVICE: + return NullVibratorManager.getInstance(); + case TEXT_CLASSIFICATION_SERVICE: case CONTENT_CAPTURE_MANAGER_SERVICE: case ALARM_SERVICE: @@ -2289,4 +2310,9 @@ public class BridgeContext extends Context { public void applyWallpaper(String wallpaperPath) { mRenderResources.setWallpaper(wallpaperPath, mConfig.isNightModeActive()); } + + @NotNull + public ThreadLocal<AnimationHandler> getAnimationHandlerThreadLocal() { + return mAnimationHandlerThreadLocal; + } } diff --git a/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java index 85bf637d00..c376429186 100644 --- a/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java +++ b/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java @@ -61,6 +61,11 @@ public class BridgePowerManager implements IPowerManager { } @Override + public boolean isBatterySaverSupported() throws RemoteException { + return true; + } + + @Override public BatterySaverPolicyConfig getFullPowerSavePolicy() { return new BatterySaverPolicyConfig.Builder().build(); } diff --git a/bridge/src/com/android/layoutlib/bridge/bars/Config.java b/bridge/src/com/android/layoutlib/bridge/bars/Config.java index d89960ea68..794990852a 100644 --- a/bridge/src/com/android/layoutlib/bridge/bars/Config.java +++ b/bridge/src/com/android/layoutlib/bridge/bars/Config.java @@ -92,8 +92,8 @@ public class Config { } public static String getTime(int platformVersion) { - if (isGreaterOrEqual(platformVersion, TIRAMISU)) { - return "13:00"; + if (isGreaterOrEqual(platformVersion, UPSIDE_DOWN_CAKE)) { + return "14:00"; } if (platformVersion < GINGERBREAD) { return "2:20"; @@ -143,6 +143,9 @@ public class Config { if (platformVersion < TIRAMISU) { return "12:00"; } + if (platformVersion < UPSIDE_DOWN_CAKE) { + return "13:00"; + } // Should never happen. return "4:04"; } diff --git a/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java index 63efec36f1..98378e63e1 100644 --- a/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java +++ b/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java @@ -24,18 +24,13 @@ import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.layoutlib.bridge.impl.ResourceHelper; -import com.android.layoutlib.bridge.resources.IconLoader; import com.android.layoutlib.bridge.resources.SysUiResources; import com.android.resources.Density; -import com.android.resources.LayoutDirection; import com.android.resources.ResourceType; import android.annotation.NonNull; import android.content.res.ColorStateList; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.BitmapFactory.Options; -import android.graphics.drawable.BitmapDrawable; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.util.TypedValue; import android.view.Gravity; @@ -45,12 +40,10 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import java.io.InputStream; - import static android.os._Original_Build.VERSION_CODES.LOLLIPOP; /** - * Base "bar" class for the window decor around the the edited layout. + * Base "bar" class for the window decor around the edited layout. * This is basically an horizontal layout that loads a given layout on creation (it is read * through {@link Class#getResourceAsStream(String)}). * <p> @@ -88,9 +81,9 @@ abstract class CustomBar extends LinearLayout { layoutName); } - protected ImageView loadIcon(ImageView imageView, String iconName, Density density) { + protected ImageView loadIcon(ImageView imageView, String iconName, Density density, int color) { return SysUiResources.loadIcon(mContext, mSimulatedPlatformVersion, imageView, iconName, - density, false); + density, false, color); } protected ImageView loadIcon(int index, String iconName, Density density, boolean isRtl) { @@ -98,39 +91,12 @@ abstract class CustomBar extends LinearLayout { if (child instanceof ImageView) { ImageView imageView = (ImageView) child; return SysUiResources.loadIcon(mContext, mSimulatedPlatformVersion, imageView, iconName, - density, isRtl); + density, isRtl, Color.WHITE); } return null; } - protected ImageView loadIcon(ImageView imageView, String iconName, Density density, - boolean isRtl) { - LayoutDirection dir = isRtl ? LayoutDirection.RTL : null; - IconLoader iconLoader = new IconLoader(iconName, density, mSimulatedPlatformVersion, dir); - InputStream stream = iconLoader.getIcon(); - - if (stream != null) { - density = iconLoader.getDensity(); - String path = iconLoader.getPath(); - // look for a cached bitmap - Bitmap bitmap = Bridge.getCachedBitmap(path, Boolean.TRUE /*isFramework*/); - if (bitmap == null) { - Options options = new Options(); - options.inDensity = density.getDpiValue(); - bitmap = BitmapFactory.decodeStream(stream, null, options); - Bridge.setCachedBitmap(path, bitmap, Boolean.TRUE /*isFramework*/); - } - - if (bitmap != null) { - BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), bitmap); - imageView.setImageDrawable(drawable); - } - } - - return imageView; - } - protected TextView setText(int index, String string) { View child = getChildAt(index); if (child instanceof TextView) { @@ -247,9 +213,7 @@ abstract class CustomBar extends LinearLayout { resource = renderResources.resolveResValue(resource); if (resource != null) { ResourceType type = resource.getResourceType(); - if (type == null || type == ResourceType.COLOR) { - // if no type is specified, the value may have been specified directly in the style - // file, rather than referencing a color resource value. + if (type == ResourceType.STYLE_ITEM || type == ResourceType.COLOR) { try { return ResourceHelper.getColor(resource.getValue()); } catch (NumberFormatException e) { diff --git a/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java index dc823f7e37..c39ec8b704 100644 --- a/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java +++ b/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java @@ -17,11 +17,13 @@ package com.android.layoutlib.bridge.bars; import com.android.ide.common.rendering.api.ILayoutLog; +import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.ResourceNamespace; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.layoutlib.bridge.impl.ParserFactory; +import com.android.layoutlib.bridge.impl.ResourceHelper; import com.android.layoutlib.bridge.resources.IconLoader; import com.android.resources.Density; @@ -42,9 +44,24 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import static android.graphics.Color.WHITE; +import static android.os._Original_Build.VERSION_CODES.M; +import static com.android.layoutlib.bridge.bars.Config.getTimeColor; +import static com.android.layoutlib.bridge.bars.Config.isGreaterOrEqual; + public class StatusBar extends CustomBar { private final int mSimulatedPlatformVersion; + /** + * Color corresponding to light_mode_icon_color_single_tone + * from frameworks/base/packages/SettingsLib/res/values/colors.xml + */ + private static final int LIGHT_ICON_COLOR = 0xffffffff; + /** + * Color corresponding to dark_mode_icon_color_single_tone + * from frameworks/base/packages/SettingsLib/res/values/colors.xml + */ + private static final int DARK_ICON_COLOR = 0x99000000; /** Status bar background color attribute name. */ private static final String ATTR_COLOR = "statusBarColor"; /** Attribute for translucency property. */ @@ -95,20 +112,45 @@ public class StatusBar extends CustomBar { return; } + int foregroundColor = getForegroundColor(simulatedPlatformVersion); // Cannot access the inside items through id because no R.id values have been // created for them. // We do know the order though. loadIcon(icons.get(0), "stat_sys_wifi_signal_4_fully." - + Config.getWifiIconType(simulatedPlatformVersion), density); - loadIcon(icons.get(1), "stat_sys_battery_100.png", density); + + Config.getWifiIconType(simulatedPlatformVersion), density,foregroundColor); + loadIcon(icons.get(1), "stat_sys_battery_100.png", density, foregroundColor); clockView.setText(Config.getTime(simulatedPlatformVersion)); - clockView.setTextColor(Config.getTimeColor(simulatedPlatformVersion)); + clockView.setTextColor(foregroundColor); + } + + private int getForegroundColor(int platformVersion) { + if (isGreaterOrEqual(platformVersion, M)) { + RenderResources renderResources = getContext().getRenderResources(); + boolean translucentBackground = + ResourceHelper.getBooleanThemeFrameworkAttrValue(renderResources, + ATTR_TRANSLUCENT, false); + if (translucentBackground) { + return WHITE; + } + boolean drawnByWindow = + ResourceHelper.getBooleanThemeFrameworkAttrValue(renderResources, + "windowDrawsSystemBarBackgrounds", false); + if (drawnByWindow) { + boolean lightStatusBar = + ResourceHelper.getBooleanThemeFrameworkAttrValue(renderResources, + "windowLightStatusBar", false); + return lightStatusBar ? DARK_ICON_COLOR : LIGHT_ICON_COLOR; + } + return WHITE; + } else { + return getTimeColor(platformVersion); + } } @Override - protected ImageView loadIcon(ImageView imageView, String iconName, Density density) { + protected ImageView loadIcon(ImageView imageView, String iconName, Density density, int color) { if (!iconName.endsWith(".xml")) { - return super.loadIcon(imageView, iconName, density); + return super.loadIcon(imageView, iconName, density, color); } // The xml is stored only in xhdpi. @@ -123,8 +165,9 @@ public class StatusBar extends CustomBar { ParserFactory.create(stream, iconName), (BridgeContext) mContext, ResourceNamespace.ANDROID); - imageView.setImageDrawable( - Drawable.createFromXml(mContext.getResources(), parser)); + Drawable drawable = Drawable.createFromXml(mContext.getResources(), parser); + drawable.setTint(color); + imageView.setImageDrawable(drawable); } catch (XmlPullParserException e) { Bridge.getLog().error(ILayoutLog.TAG_BROKEN, "Unable to draw wifi icon", e, null, null); diff --git a/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java index c2430a8959..8048ff1e1a 100644 --- a/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java +++ b/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java @@ -32,6 +32,7 @@ import com.android.tools.layoutlib.annotations.NotNull; import com.android.tools.layoutlib.annotations.Nullable; import com.android.tools.layoutlib.annotations.VisibleForTesting; +import android.animation.AnimationHandler; import android.animation.PropertyValuesHolder_Accessor; import android.content.res.Configuration; import android.graphics.drawable.AdaptiveIconDrawable_Delegate; @@ -51,6 +52,7 @@ import java.util.WeakHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; +import static android.os._Original_Build.VERSION.SDK_INT; import static com.android.ide.common.rendering.api.Result.Status.ERROR_LOCK_INTERRUPTED; import static com.android.ide.common.rendering.api.Result.Status.ERROR_TIMEOUT; import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; @@ -68,6 +70,12 @@ import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; * */ public abstract class RenderAction<T extends RenderParams> { + /** + * Static field to store an SDK version coming from the render configuration. + * This is to be accessed when wanting to know the simulated SDK version instead + * of Build.VERSION.SDK_INT. + */ + public static int sSimulatedSdk; private static final Set<String> COMPOSE_CLASS_FQNS = Set.of("androidx.compose.ui.tooling.ComposeViewAdapter", @@ -99,6 +107,7 @@ public abstract class RenderAction<T extends RenderParams> { */ protected RenderAction(T params) { mParams = params; + sSimulatedSdk = SDK_INT; } /** @@ -276,6 +285,7 @@ public abstract class RenderAction<T extends RenderParams> { ILayoutLog currentLog = mParams.getLog(); Bridge.setLog(currentLog); mContext.getRenderResources().setLogger(currentLog); + AnimationHandler.sAnimatorHandler = mContext.getAnimationHandlerThreadLocal(); } /** @@ -467,6 +477,14 @@ public abstract class RenderAction<T extends RenderParams> { if (sCurrentContext != null) { // quit HandlerThread created during this session. HandlerThread_Delegate.cleanUp(sCurrentContext); + + AnimationHandler animationHandler = + sCurrentContext.getAnimationHandlerThreadLocal().get(); + if (animationHandler != null) { + animationHandler.mDelayedCallbackStartTime.clear(); + animationHandler.mAnimationCallbacks.clear(); + animationHandler.mCommitCallbacks.clear(); + } } sCurrentContext = null; diff --git a/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java b/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java index 6a6e184617..0dd35ce055 100644 --- a/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java +++ b/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java @@ -39,6 +39,7 @@ import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -78,7 +79,7 @@ public class RenderDrawable extends RenderAction<DrawableParams> { return Status.ERROR_NOT_A_DRAWABLE.createResult(); } - Drawable d = ResourceHelper.getDrawable(drawableResource, context); + Drawable d = ResourceHelper.getDrawable(drawableResource, context, context.getTheme()); if (d == null) { return Status.ERROR_NOT_A_DRAWABLE.createResult(); } @@ -128,15 +129,6 @@ public class RenderDrawable extends RenderAction<DrawableParams> { // Use screen size when either intrinsic width or height isn't available. w = screenWidth; h = screenHeight; - } else if (w > screenWidth || h > screenHeight) { - // If image wouldn't fit to the screen, resize it to avoid cropping. - - // We need to find scale such that scale * w <= screenWidth, scale * h <= screenHeight. - double scale = Math.min((double) screenWidth / w, (double) screenHeight / h); - - // scale * w / scale * h = w / h, so, proportions are preserved. - w = (int) Math.floor(scale * w); - h = (int) Math.floor(scale * h); } int w_spec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY); @@ -149,46 +141,30 @@ public class RenderDrawable extends RenderAction<DrawableParams> { // Pre-draw setup. AttachInfo_Accessor.dispatchOnPreDraw(content); - // Draw into a new image. - BufferedImage image = getImage(w, h); - - // Create an Android bitmap around the BufferedImage. - Bitmap bitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(), - Config.ARGB_8888); - bitmap.setPixels(image.getRGB(0, 0, image.getWidth(), image.getHeight(), - null, 0, image.getWidth()), 0, image.getWidth(), 0, 0, image - .getWidth(), image.getHeight()); - - // Create a Canvas around the Android bitmap. + Bitmap bitmap = Bitmap.createBitmap(w, h, Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); canvas.setDensity(hardwareConfig.getDensity().getDpiValue()); // Draw. content.draw(canvas); - int[] pixels = new int[image.getWidth() * image.getHeight()]; - bitmap.getPixels(pixels, 0, image.getWidth(), 0, 0, image.getWidth(), - image.getHeight()); - image.setRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth()); - // Detach root from window after draw. - AttachInfo_Accessor.detachFromWindow(content); + if (w > screenWidth || h > screenHeight) { + // If image wouldn't fit to the screen, resize it to avoid cropping. - return image; - } + // We need to find scale such that scale * w <= screenWidth, scale * h <= screenHeight. + double scale = Math.min((double) screenWidth / w, (double) screenHeight / h); + bitmap = Bitmap.createScaledBitmap(bitmap, (int) (w * scale), (int) (h * scale), true); + } - @NonNull - protected BufferedImage getImage(int w, int h) { - BufferedImage image = new BufferedImage(w > 0 ? w : 1, - h > 0 ? h : 1, + // Copy bitmap into BufferedImage. + BufferedImage image = new BufferedImage(bitmap.getWidth(), bitmap.getHeight(), BufferedImage.TYPE_INT_ARGB); - Graphics2D gc = image.createGraphics(); - gc.setComposite(AlphaComposite.Src); - - gc.setColor(new Color(0x00000000, true)); - gc.fillRect(0, 0, w, h); + int[] imageData = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + bitmap.getPixels(imageData, 0, image.getWidth(), 0, 0, image.getWidth(), + image.getHeight()); - // done - gc.dispose(); + // Detach root from window after draw. + AttachInfo_Accessor.detachFromWindow(content); return image; } diff --git a/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index ae5e401edc..faeeb19f1a 100644 --- a/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -50,7 +50,6 @@ import com.android.tools.idea.validator.ValidatorHierarchy; import com.android.tools.idea.validator.hierarchy.CustomHierarchyHelper; import com.android.tools.layoutlib.annotations.NotNull; -import android.animation.AnimationHandler; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -92,7 +91,9 @@ import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; +import static android.os._Original_Build.VERSION.SDK_INT; import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION; import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED; import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; @@ -198,7 +199,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } /** - * Measures the the current layout if needed (see {@link #invalidateRenderingSize}). + * Measures the current layout if needed (see {@link #invalidateRenderingSize}). */ private void measureLayout(@NonNull SessionParams params) { // only do the screen measure when needed. @@ -310,6 +311,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { SessionParams params = getParams(); BridgeContext context = getContext(); + int simulatedVersion = params.getSimulatedPlatformVersion(); + sSimulatedSdk = simulatedVersion > 0 ? simulatedVersion : SDK_INT; + if (Bridge.isLocaleRtl(params.getLocale())) { if (!params.isRtlSupported()) { Bridge.getLog().warning(ILayoutLog.TAG_RTL_NOT_ENABLED, @@ -368,8 +372,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } mSystemViewInfoList = - visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(), - false); + visitAllChildren(mViewRoot, 0, 0, params, false); return SUCCESS.createResult(); } catch (PostInflateException e) { @@ -480,6 +483,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { SessionParams params = getParams(); + int simulatedVersion = params.getSimulatedPlatformVersion(); + sSimulatedSdk = simulatedVersion > 0 ? simulatedVersion : SDK_INT; + try { if (mViewRoot == null) { return ERROR_NOT_INFLATED.createResult(); @@ -570,8 +576,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } mSystemViewInfoList = - visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(), - false); + visitAllChildren(mViewRoot, 0, 0, params, false); boolean enableLayoutValidation = Boolean.TRUE.equals(params.getFlag(RenderParamsFlags.FLAG_ENABLE_LAYOUT_VALIDATOR)); boolean enableLayoutValidationImageCheck = Boolean.TRUE.equals( @@ -853,15 +858,16 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * * @return {@code ViewInfo} containing the bounds of the view and it children otherwise. */ - private ViewInfo visit(View view, int hOffset, int vOffset, boolean setExtendedInfo, + private ViewInfo visit(View view, int hOffset, int vOffset, SessionParams params, boolean isContentFrame) { - ViewInfo result = createViewInfo(view, hOffset, vOffset, setExtendedInfo, isContentFrame); + ViewInfo result = createViewInfo(view, hOffset, vOffset, params.getExtendedViewInfoMode(), + isContentFrame); if (view instanceof ViewGroup) { ViewGroup group = ((ViewGroup) view); result.setChildren(visitAllChildren(group, isContentFrame ? 0 : hOffset, isContentFrame ? 0 : vOffset, - setExtendedInfo, isContentFrame)); + params, isContentFrame)); } return result; } @@ -880,7 +886,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * part of the system decor. */ private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int hOffset, int vOffset, - boolean setExtendedInfo, boolean isContentFrame) { + SessionParams params, boolean isContentFrame) { if (viewGroup == null) { return null; } @@ -896,8 +902,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { List<ViewInfo> childrenWithOffset = new ArrayList<>(childCount); for (int i = 0; i < childCount; i++) { ViewInfo[] childViewInfo = - visitContentRoot(viewGroup.getChildAt(i), hOffset, vOffset, - setExtendedInfo); + visitContentRoot(viewGroup.getChildAt(i), hOffset, vOffset, params); childrenWithoutOffset.add(childViewInfo[0]); childrenWithOffset.add(childViewInfo[1]); } @@ -906,7 +911,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } else { List<ViewInfo> children = new ArrayList<>(childCount); for (int i = 0; i < childCount; i++) { - children.add(visit(viewGroup.getChildAt(i), hOffset, vOffset, setExtendedInfo, + children.add(visit(viewGroup.getChildAt(i), hOffset, vOffset, params, isContentFrame)); } return children; @@ -920,26 +925,32 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * get the right bounds if the {@code ViewInfo} hierarchy is accessed from * {@code mViewInfoList}. When the hierarchy is accessed via {@code mSystemViewInfoList}, the * offset is not needed. + * If a custom parser was passed inside the {@link SessionParams} argument, this will be used + * to generate the {@link ViewInfo}s. Otherwise, {@link RenderSessionImpl#visitAllChildren} + * will be used. * * @return an array of length two, with ViewInfo at index 0 is without offset and ViewInfo at * index 1 is with the offset. */ @NonNull - private ViewInfo[] visitContentRoot(View view, int hOffset, int vOffset, - boolean setExtendedInfo) { + private ViewInfo[] visitContentRoot(View view, int hOffset, int vOffset, SessionParams params) { ViewInfo[] result = new ViewInfo[2]; if (view == null) { return result; } + boolean setExtendedInfo = params.getExtendedViewInfoMode(); result[0] = createViewInfo(view, 0, 0, setExtendedInfo, true); result[1] = createViewInfo(view, hOffset, vOffset, setExtendedInfo, true); - if (view instanceof ViewGroup) { - List<ViewInfo> children = - visitAllChildren((ViewGroup) view, 0, 0, setExtendedInfo, true); - result[0].setChildren(children); - result[1].setChildren(children); + Function<Object, List<ViewInfo>> customParser = params.getCustomContentHierarchyParser(); + List<ViewInfo> children = null; + if (customParser != null) { + children = customParser.apply(view); + } else if (view instanceof ViewGroup) { + children = visitAllChildren((ViewGroup) view, 0, 0, params, true); } + result[0].setChildren(children); + result[1].setChildren(children); return result; } @@ -975,13 +986,13 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { shiftY + view.getTop(), shiftX + view.getRight(), shiftY + view.getBottom(), - view, view.getLayoutParams()); + view, null, view.getLayoutParams()); } else { // We are part of the system decor. SystemViewInfo r = new SystemViewInfo(view.getClass().getName(), getViewKey(view), view.getLeft(), view.getTop(), view.getRight(), - view.getBottom(), view, view.getLayoutParams()); + view.getBottom(), view, null, view.getLayoutParams()); result = r; // We currently mark three kinds of views: // 1. Menus in the Action Bar @@ -1198,12 +1209,6 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { mImage = null; // detachFromWindow might create Handler callbacks, thus before Handler_Delegate.dispose AttachInfo_Accessor.detachFromWindow(mViewRoot); - AnimationHandler animationHandler = AnimationHandler.sAnimatorHandler.get(); - if (animationHandler != null) { - animationHandler.mDelayedCallbackStartTime.clear(); - animationHandler.mAnimationCallbacks.clear(); - animationHandler.mCommitCallbacks.clear(); - } getContext().getSessionInteractiveData().dispose(); if (mViewInfoList != null) { mViewInfoList.clear(); diff --git a/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java b/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java index 9fea1677d5..6f9092cc12 100644 --- a/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java +++ b/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java @@ -32,8 +32,9 @@ public class SystemViewInfo extends ViewInfo { } public SystemViewInfo(String name, Object cookie, int left, int top, - int right, int bottom, Object viewObject, Object layoutParamsObject) { - super(name, cookie, left, top, right, bottom, viewObject, + int right, int bottom, Object viewObject, Object accessibilityObject, + Object layoutParamsObject) { + super(name, cookie, left, top, right, bottom, viewObject, accessibilityObject, layoutParamsObject); } diff --git a/bridge/src/com/android/layoutlib/bridge/resources/SysUiResources.java b/bridge/src/com/android/layoutlib/bridge/resources/SysUiResources.java index 84ed6a04c4..f8884d4b2e 100644 --- a/bridge/src/com/android/layoutlib/bridge/resources/SysUiResources.java +++ b/bridge/src/com/android/layoutlib/bridge/resources/SysUiResources.java @@ -36,7 +36,6 @@ import android.graphics.BitmapFactory.Options; import android.graphics.drawable.BitmapDrawable; import android.widget.ImageView; -import java.io.IOException; import java.io.InputStream; public class SysUiResources { @@ -67,10 +66,8 @@ public class SysUiResources { return null; } - public static ImageView loadIcon(Context context, int api, ImageView imageView, String - iconName, - Density density, boolean - isRtl) { + public static ImageView loadIcon(Context context, int api, ImageView imageView, + String iconName, Density density, boolean isRtl, int color) { LayoutDirection dir = isRtl ? LayoutDirection.RTL : null; IconLoader iconLoader = new IconLoader(iconName, density, api, dir); @@ -90,6 +87,7 @@ public class SysUiResources { if (bitmap != null) { BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap); + drawable.setTint(color); imageView.setImageDrawable(drawable); } } diff --git a/bridge/tests/res/testApp/MyApplication/golden/activity.png b/bridge/tests/res/testApp/MyApplication/golden/activity.png Binary files differindex a05349d73c..4546682d06 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/activity.png +++ b/bridge/tests/res/testApp/MyApplication/golden/activity.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon.png b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon.png Binary files differindex 2d6938aa28..b438464c6c 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon.png +++ b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_circle.png b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_circle.png Binary files differindex 5570291230..e939a572ec 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_circle.png +++ b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_circle.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_green.png b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_green.png Binary files differindex 61f1f18af1..b70c65d05e 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_green.png +++ b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_green.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_orange.png b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_orange.png Binary files differindex dd1dd57027..31647c615d 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_orange.png +++ b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_dynamic_orange.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_rounded_corners.png b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_rounded_corners.png Binary files differindex e0b60374cd..67deb6e4d4 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_rounded_corners.png +++ b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_rounded_corners.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_squircle.png b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_squircle.png Binary files differindex 3e41ccc2c1..6e63ef2286 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_squircle.png +++ b/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon_squircle.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png b/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png Binary files differindex 48a40cd191..fb5e970dc4 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png +++ b/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png b/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png Binary files differindex f8cec3261b..5d452ef48d 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png +++ b/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png b/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png Binary files differindex c00823d326..3887e292e3 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png +++ b/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png b/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png Binary files differindex db0b343eb3..9588148737 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png +++ b/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/asset.png b/bridge/tests/res/testApp/MyApplication/golden/asset.png Binary files differindex b6193f61e4..f4467d6c1d 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/asset.png +++ b/bridge/tests/res/testApp/MyApplication/golden/asset.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/auto-scale-image.png b/bridge/tests/res/testApp/MyApplication/golden/auto-scale-image.png Binary files differindex 6a23995415..7cabc39941 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/auto-scale-image.png +++ b/bridge/tests/res/testApp/MyApplication/golden/auto-scale-image.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/bitmap_decoder.png b/bridge/tests/res/testApp/MyApplication/golden/bitmap_decoder.png Binary files differindex 20f15a9ecd..5be2d1a800 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/bitmap_decoder.png +++ b/bridge/tests/res/testApp/MyApplication/golden/bitmap_decoder.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/context_theme_wrapper.png b/bridge/tests/res/testApp/MyApplication/golden/context_theme_wrapper.png Binary files differindex 323d51493e..9e08e22488 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/context_theme_wrapper.png +++ b/bridge/tests/res/testApp/MyApplication/golden/context_theme_wrapper.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/dark_status_bar.png b/bridge/tests/res/testApp/MyApplication/golden/dark_status_bar.png Binary files differnew file mode 100644 index 0000000000..34ff1adccf --- /dev/null +++ b/bridge/tests/res/testApp/MyApplication/golden/dark_status_bar.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/expand_horz_layout.png b/bridge/tests/res/testApp/MyApplication/golden/expand_horz_layout.png Binary files differindex 5ed270620f..7b1f1f1e02 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/expand_horz_layout.png +++ b/bridge/tests/res/testApp/MyApplication/golden/expand_horz_layout.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/expand_vert_layout.png b/bridge/tests/res/testApp/MyApplication/golden/expand_vert_layout.png Binary files differindex 80e72b0199..d6a4c5c586 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/expand_vert_layout.png +++ b/bridge/tests/res/testApp/MyApplication/golden/expand_vert_layout.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/four_corners.png b/bridge/tests/res/testApp/MyApplication/golden/four_corners.png Binary files differindex 82e40ac58e..4e7feb443f 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/four_corners.png +++ b/bridge/tests/res/testApp/MyApplication/golden/four_corners.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/light_status_bar.png b/bridge/tests/res/testApp/MyApplication/golden/light_status_bar.png Binary files differnew file mode 100644 index 0000000000..a37d8dee26 --- /dev/null +++ b/bridge/tests/res/testApp/MyApplication/golden/light_status_bar.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/ninepatch_background.png b/bridge/tests/res/testApp/MyApplication/golden/ninepatch_background.png Binary files differindex fefb3b7181..6403637078 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/ninepatch_background.png +++ b/bridge/tests/res/testApp/MyApplication/golden/ninepatch_background.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/ondraw_crash.png b/bridge/tests/res/testApp/MyApplication/golden/ondraw_crash.png Binary files differindex d69667c6b7..4051b05ca2 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/ondraw_crash.png +++ b/bridge/tests/res/testApp/MyApplication/golden/ondraw_crash.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/onmeasure_crash.png b/bridge/tests/res/testApp/MyApplication/golden/onmeasure_crash.png Binary files differindex 4d9f7769d9..e27ae9eb39 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/onmeasure_crash.png +++ b/bridge/tests/res/testApp/MyApplication/golden/onmeasure_crash.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/shadow_scrollview_test.png b/bridge/tests/res/testApp/MyApplication/golden/shadow_scrollview_test.png Binary files differindex 19e65aa167..55a9982b5d 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/shadow_scrollview_test.png +++ b/bridge/tests/res/testApp/MyApplication/golden/shadow_scrollview_test.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/shadow_sizes_test.png b/bridge/tests/res/testApp/MyApplication/golden/shadow_sizes_test.png Binary files differindex 84b7299a56..6093824921 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/shadow_sizes_test.png +++ b/bridge/tests/res/testApp/MyApplication/golden/shadow_sizes_test.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/shadows_test_rounded_edge.png b/bridge/tests/res/testApp/MyApplication/golden/shadows_test_rounded_edge.png Binary files differindex 7d9a51d793..f1437af29a 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/shadows_test_rounded_edge.png +++ b/bridge/tests/res/testApp/MyApplication/golden/shadows_test_rounded_edge.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png b/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png Binary files differindex 2b2aab8a0e..1300dd0cca 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png +++ b/bridge/tests/res/testApp/MyApplication/golden/simple_activity.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png b/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png Binary files differindex 38924a322c..7852c9e323 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png +++ b/bridge/tests/res/testApp/MyApplication/golden/simple_activity_noactionbar.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/software_layer.png b/bridge/tests/res/testApp/MyApplication/golden/software_layer.png Binary files differnew file mode 100644 index 0000000000..70465cf27b --- /dev/null +++ b/bridge/tests/res/testApp/MyApplication/golden/software_layer.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png Binary files differindex 5832c67a71..03da31efc0 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png +++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_91383.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_gradient.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_gradient.png Binary files differindex 769406e5fb..67e502fccd 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_gradient.png +++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_gradient.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_radial_gradient.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_radial_gradient.png Binary files differindex 6882d79b87..4b1425ac89 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_radial_gradient.png +++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_radial_gradient.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_with_tint_in_image_view.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_with_tint_in_image_view.png Binary files differindex 759d535f6e..055af89473 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_with_tint_in_image_view.png +++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_with_tint_in_image_view.png diff --git a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_with_tint_itself.png b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_with_tint_itself.png Binary files differindex 4396a51816..99e37aed57 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_with_tint_itself.png +++ b/bridge/tests/res/testApp/MyApplication/golden/vector_drawable_with_tint_itself.png diff --git a/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/SoftwareTextView.java b/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/SoftwareTextView.java new file mode 100644 index 0000000000..3d8cb0c75d --- /dev/null +++ b/bridge/tests/res/testApp/MyApplication/src/main/myapplication.widgets/SoftwareTextView.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 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.layoutlib.test.myapplication.widgets; + +import android.content.Context; +import android.graphics.Color; +import android.util.AttributeSet; +import android.widget.TextView; + +public class SoftwareTextView extends TextView { + + public SoftwareTextView(Context context) { + super(context); + init(); + } + + public SoftwareTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public SoftwareTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + public SoftwareTextView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + private void init() { + setLayerType(LAYER_TYPE_SOFTWARE, null); + setBackgroundColor(Color.RED); + setText("Software Layer"); + } +} diff --git a/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml b/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml index 7b338d1dc6..d75aab1c1c 100644 --- a/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml +++ b/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml @@ -16,4 +16,13 @@ <item name="android:windowBackground">@drawable/theme_attribute_drawable</item> </style> + <style name="LightStatusBarTheme" parent="android:Theme.Material.Light.DarkActionBar"> + <item name="android:statusBarColor">#ffff0000</item> + <item name="android:windowLightStatusBar">true</item> + </style> + + <style name="DarkStatusBarTheme" parent="android:Theme.Material.Light.DarkActionBar"> + <item name="android:statusBarColor">#ffff0000</item> + </style> + </resources> diff --git a/bridge/tests/run_tests.sh b/bridge/tests/run_tests.sh index 91e88b6c94..bf338102bc 100755 --- a/bridge/tests/run_tests.sh +++ b/bridge/tests/run_tests.sh @@ -9,7 +9,7 @@ echo "BASE_DIR: $BASE_DIR" readonly FAILURE_DIR=layoutlib-test-failures readonly FAILURE_ZIP=layoutlib-test-failures.zip -readonly CLEAN_TMP_FILES=0 +readonly CLEAN_TMP_FILES=1 readonly USE_SOONG=1 readonly APP_NAME="regression" @@ -76,6 +76,7 @@ set +x # Create zip of all failure screenshots +rm -f ${OUT_DIR}/${FAILURE_ZIP} if [[ -d "${OUT_DIR}/${FAILURE_DIR}" ]]; then zip -q -j -r ${OUT_DIR}/${FAILURE_ZIP} ${OUT_DIR}/${FAILURE_DIR} fi diff --git a/bridge/tests/src/android/util/BridgeXmlPullAttributesTest.java b/bridge/tests/src/android/util/BridgeXmlPullAttributesTest.java index f995d1abaa..a40dfed432 100644 --- a/bridge/tests/src/android/util/BridgeXmlPullAttributesTest.java +++ b/bridge/tests/src/android/util/BridgeXmlPullAttributesTest.java @@ -20,8 +20,10 @@ import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.ResourceNamespace; import com.android.ide.common.rendering.api.ResourceNamespace.Resolver; +import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.BridgeConstants; import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.tools.layoutlib.annotations.NotNull; import org.junit.Test; import org.xmlpull.v1.XmlPullParser; @@ -34,12 +36,10 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class BridgeXmlPullAttributesTest { - - @Test - public void testGetAttributeIntValueForEnums() { - RenderResources renderResources = new RenderResources(); - + @NotNull + private static XmlPullParser prepareParser() { XmlPullParser parser = mock(XmlPullParser.class); + when(parser.getAttributeValue(BridgeConstants.NS_RESOURCES, "layout_width")) .thenReturn("match_parent"); when(parser.getAttributeName(0)).thenReturn("layout_width"); @@ -50,34 +50,53 @@ public class BridgeXmlPullAttributesTest { when(parser.getAttributeName(1)).thenReturn("my_custom_attr"); when(parser.getAttributeNamespace(1)).thenReturn(BridgeConstants.NS_APP_RES_AUTO); + return parser; + } + + @NotNull + private static BridgeContext prepareContext() { BridgeContext context = mock(BridgeContext.class); + RenderResources renderResources = new RenderResources() { + @Override + public ResourceValue resolveResValue(ResourceValue value) { + // Simulate behaviour from the actual resolver where a failed resolution will + // return the passed value. + return value; + } + }; when(context.getRenderResources()).thenReturn(renderResources); - LayoutlibCallback callback = mock(LayoutlibCallback.class); when(callback.getImplicitNamespaces()).thenReturn(Resolver.EMPTY_RESOLVER); when(context.getLayoutlibCallback()).thenReturn(callback); - BridgeXmlPullAttributes attributes = new BridgeXmlPullAttributes( - parser, - context, - ResourceNamespace.RES_AUTO, - attrName -> { - if ("layout_width".equals(attrName)) { - return ImmutableMap.of( - "match_parent", 123); - } - return ImmutableMap.of(); - }, - (ns, attrName) -> { - if ("my_custom_attr".equals(attrName)) { - return ImmutableMap.of( - "a", 1, - "b", 2 - ); - } - return ImmutableMap.of(); - }); + return context; + } + + private final XmlPullParser parser = prepareParser(); + private final BridgeContext context = prepareContext(); + private final BridgeXmlPullAttributes attributes = new BridgeXmlPullAttributes( + parser, + context, + ResourceNamespace.RES_AUTO, + attrName -> { + if ("layout_width".equals(attrName)) { + return ImmutableMap.of( + "match_parent", 123); + } + return ImmutableMap.of(); + }, + (ns, attrName) -> { + if ("my_custom_attr".equals(attrName)) { + return ImmutableMap.of( + "a", 1, + "b", 2 + ); + } + return ImmutableMap.of(); + }); + @Test + public void testGetAttributeIntValueForEnums() { // Test a framework defined enum attribute assertEquals(123, attributes.getAttributeIntValue(BridgeConstants.NS_RESOURCES, "layout_width", 500)); @@ -115,4 +134,11 @@ public class BridgeXmlPullAttributesTest { "my_other_attr", 500)); } + @Test + public void testNotExistingAttributes() { + assertEquals(501, attributes.getAttributeUnsignedIntValue(BridgeConstants.NS_APP_RES_AUTO, + "my_other_attr", 501)); + assertEquals(502, attributes.getAttributeResourceValue(BridgeConstants.NS_APP_RES_AUTO, + "my_other_attr", 502)); + } } diff --git a/bridge/tests/src/com/android/layoutlib/bridge/android/AccessibilityTest.java b/bridge/tests/src/com/android/layoutlib/bridge/android/AccessibilityTest.java index ed90d9b60c..149680868e 100644 --- a/bridge/tests/src/com/android/layoutlib/bridge/android/AccessibilityTest.java +++ b/bridge/tests/src/com/android/layoutlib/bridge/android/AccessibilityTest.java @@ -19,6 +19,7 @@ package com.android.layoutlib.bridge.android; import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.SessionParams; +import com.android.ide.common.rendering.api.ViewInfo; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.intensive.LayoutLibTestCallback; import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator; @@ -28,9 +29,12 @@ import org.junit.BeforeClass; import org.junit.Test; import android.view.View; +import android.view.ViewGroup; import android.view.accessibility.AccessibilityNodeInfo; import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -71,4 +75,46 @@ public class AccessibilityTest extends RenderTestBase { session.dispose(); } } + + @Test + public void customHierarchyParserTest() throws FileNotFoundException, + ClassNotFoundException { + LayoutPullParser parser = createParserFromPath("allwidgets.xml"); + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); + layoutLibCallback.initResources(); + SessionParams params = getSessionParamsBuilder() + .setParser(parser) + .setConfigGenerator(ConfigGenerator.NEXUS_5) + .setCallback(layoutLibCallback) + .build(); + params.setCustomContentHierarchyParser(viewObject -> { + List<ViewInfo> result = new ArrayList<>(); + if (viewObject instanceof ViewGroup) { + ViewGroup view = (ViewGroup)viewObject; + for (int i = 0; i < view.getChildCount(); i++) { + View child = view.getChildAt(i); + ViewInfo childInfo = + new ViewInfo(child.toString(), null, child.getLeft(), child.getTop(), + child.getRight(), child.getBottom(), child, + child.createAccessibilityNodeInfo(), null); + childInfo.setChildren(null); + result.add(childInfo); + } + } + return result; + }); + RenderSession session = sBridge.createSession(params); + try { + Result renderResult = session.render(50000); + assertTrue(renderResult.isSuccess()); + ViewInfo contentRootViewInfo = session.getRootViews().get(0); + contentRootViewInfo.getChildren().forEach(child -> { + assertNotNull(child.getAccessibilityObject()); + assertEquals(0, child.getChildren().size()); + }); + } finally { + session.dispose(); + } + } } diff --git a/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeContextTest.java b/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeContextTest.java index b12adc93e4..83a4f80111 100644 --- a/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeContextTest.java +++ b/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeContextTest.java @@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.RenderAction; import com.android.layoutlib.bridge.impl.RenderActionTestUtil; import com.android.layoutlib.bridge.intensive.LayoutLibTestCallback; +import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator; import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser; import org.junit.BeforeClass; @@ -33,10 +34,13 @@ import android.content.res.Configuration; import android.content.res.TypedArray; import android.os.PowerManager; import android.util.DisplayMetrics; +import android.util.TypedValue; import android.view.ContextThemeWrapper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -174,12 +178,12 @@ public class BridgeContextTest extends RenderTestBase { ((DynamicRenderResources) context.getRenderResources()).setWallpaper( "/com/android/layoutlib/testdata/wallpaper1.webp", configuration.isNightModeActive()); - assertEquals(-13226195, context.getResources().getColor(android.R.color.system_neutral1_800, null)); + assertEquals(-13029845, context.getResources().getColor(android.R.color.system_neutral1_800, null)); ((DynamicRenderResources) context.getRenderResources()).setWallpaper( "/com/android/layoutlib/testdata/wallpaper2.webp", configuration.isNightModeActive()); - assertEquals(-13749969, context.getResources().getColor(android.R.color.system_neutral1_800, null)); + assertEquals(-13946321, context.getResources().getColor(android.R.color.system_neutral1_800, null)); } finally { context.disposeResources(); } @@ -204,4 +208,63 @@ public class BridgeContextTest extends RenderTestBase { assertFalse(powerManager.isPowerSaveMode()); assertTrue(powerManager.isInteractive()); } + + @Test + public void testTypedValue() throws Exception { + // Setup + // Create the layout pull parser for our resources (empty.xml can not be part of the test + // app as it won't compile). + LayoutPullParser parser = LayoutPullParser.createFromPath("/empty.xml"); + // Create LayoutLibCallback. + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(RenderTestBase.getLogger(), mDefaultClassLoader); + layoutLibCallback.initResources(); + SessionParams params = getSessionParamsBuilder() + .setConfigGenerator(ConfigGenerator.NEXUS_4) + .setParser(parser) + .setCallback(layoutLibCallback) + .build(); + DisplayMetrics metrics = new DisplayMetrics(); + Configuration configuration = RenderAction.getConfiguration(params); + + BridgeContext mContext = + new BridgeContext(params.getProjectKey(), metrics, params.getResources(), + params.getAssets(), params.getLayoutlibCallback(), configuration, + params.getTargetSdkVersion(), params.isRtlSupported()); + + TypedValue outValue = new TypedValue(); + mContext.resolveThemeAttribute(android.R.attr.colorPrimary, outValue, true); + assertEquals(TypedValue.TYPE_INT_COLOR_ARGB8, outValue.type); + assertNotEquals(0, outValue.data); + + outValue = new TypedValue(); + mContext.resolveThemeAttribute(android.R.attr.colorError, outValue, true); + assertEquals(TypedValue.TYPE_INT_COLOR_RGB4, outValue.type); + assertEquals(-65536, outValue.data); + + outValue = new TypedValue(); + mContext.resolveThemeAttribute(attr.colorActivatedHighlight, outValue, true); + assertEquals(TypedValue.TYPE_INT_COLOR_ARGB4, outValue.type); + assertEquals(-872349952, outValue.data); + + outValue = new TypedValue(); + mContext.resolveThemeAttribute(android.R.attr.isLightTheme, outValue, true); + assertEquals(TypedValue.TYPE_INT_BOOLEAN, outValue.type); + assertEquals(1, outValue.data); + + outValue = new TypedValue(); + mContext.resolveThemeAttribute(android.R.attr.scrollbarFadeDuration, outValue, true); + assertEquals(TypedValue.TYPE_INT_DEC, outValue.type); + assertEquals(250, outValue.data); + + outValue = new TypedValue(); + mContext.resolveThemeAttribute(android.R.attr.scrollbarThumbHorizontal, outValue, true); + assertEquals(TypedValue.TYPE_STRING, outValue.type); + assertNotNull(outValue.string); + + outValue = new TypedValue(); + mContext.resolveThemeAttribute(android.R.attr.actionBarSize, outValue, true); + assertEquals(TypedValue.TYPE_DIMENSION, outValue.type); + assertEquals(14337, outValue.data); + } } diff --git a/bridge/tests/src/com/android/layoutlib/bridge/android/DynamicRenderResourcesTest.java b/bridge/tests/src/com/android/layoutlib/bridge/android/DynamicRenderResourcesTest.java index 16a809b3e7..ec7aa768fd 100644 --- a/bridge/tests/src/com/android/layoutlib/bridge/android/DynamicRenderResourcesTest.java +++ b/bridge/tests/src/com/android/layoutlib/bridge/android/DynamicRenderResourcesTest.java @@ -50,9 +50,9 @@ public class DynamicRenderResourcesTest extends RenderTestBase { assertEquals(-4478092, (int)dynamicColorMap.get("system_accent3_300")); assertEquals(-12963835, (int)dynamicColorMap.get("system_accent3_800")); assertEquals(-1, (int)dynamicColorMap.get("system_neutral1_0")); - assertEquals(-266518, (int)dynamicColorMap.get("system_neutral1_50")); - assertEquals(-4937306, (int)dynamicColorMap.get("system_neutral1_300")); - assertEquals(-13226195, (int)dynamicColorMap.get("system_neutral1_800")); + assertEquals(-4632, (int)dynamicColorMap.get("system_neutral1_50")); + assertEquals(-4675421, (int)dynamicColorMap.get("system_neutral1_300")); + assertEquals(-13029845, (int)dynamicColorMap.get("system_neutral1_800")); assertEquals(-1, (int)dynamicColorMap.get("system_neutral2_0")); assertEquals(-4632, (int)dynamicColorMap.get("system_neutral2_50")); assertEquals(-4413535, (int)dynamicColorMap.get("system_neutral2_300")); diff --git a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java index 7b9c163634..5e5170e92e 100644 --- a/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java +++ b/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java @@ -46,7 +46,6 @@ import org.junit.Test; import org.kxml2.io.KXmlParser; import org.xmlpull.v1.XmlPullParser; -import android.R.attr; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.res.AssetManager; @@ -57,7 +56,6 @@ import android.content.res.Resources_Delegate; import android.graphics.Color; import android.util.DisplayMetrics; import android.util.StateSet; -import android.util.TypedValue; import android.widget.Button; import android.widget.LinearLayout; @@ -75,7 +73,6 @@ import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -974,61 +971,6 @@ public class RenderTests extends RenderTestBase { } @Test - public void testTypedValue() throws Exception { - // Setup - // Create the layout pull parser for our resources (empty.xml can not be part of the test - // app as it won't compile). - LayoutPullParser parser = LayoutPullParser.createFromPath("/empty.xml"); - // Create LayoutLibCallback. - LayoutLibTestCallback layoutLibCallback = - new LayoutLibTestCallback(RenderTestBase.getLogger(), mDefaultClassLoader); - layoutLibCallback.initResources(); - SessionParams params = getSessionParamsBuilder() - .setConfigGenerator(ConfigGenerator.NEXUS_4) - .setParser(parser) - .setCallback(layoutLibCallback) - .build(); - DisplayMetrics metrics = new DisplayMetrics(); - Configuration configuration = RenderAction.getConfiguration(params); - - BridgeContext mContext = - new BridgeContext(params.getProjectKey(), metrics, params.getResources(), - params.getAssets(), params.getLayoutlibCallback(), configuration, - params.getTargetSdkVersion(), params.isRtlSupported()); - - TypedValue outValue = new TypedValue(); - mContext.resolveThemeAttribute(android.R.attr.colorPrimary, outValue, true); - assertEquals(TypedValue.TYPE_INT_COLOR_ARGB8, outValue.type); - assertNotEquals(0, outValue.data); - - outValue = new TypedValue(); - mContext.resolveThemeAttribute(android.R.attr.colorError, outValue, true); - assertEquals(TypedValue.TYPE_INT_COLOR_RGB4, outValue.type); - assertEquals(-65536, outValue.data); - - outValue = new TypedValue(); - mContext.resolveThemeAttribute(attr.colorActivatedHighlight, outValue, true); - assertEquals(TypedValue.TYPE_INT_COLOR_ARGB4, outValue.type); - assertEquals(-872349952, outValue.data); - - outValue = new TypedValue(); - mContext.resolveThemeAttribute(android.R.attr.isLightTheme, outValue, true); - assertEquals(TypedValue.TYPE_INT_BOOLEAN, outValue.type); - assertEquals(1, outValue.data); - - outValue = new TypedValue(); - mContext.resolveThemeAttribute(android.R.attr.scrollbarFadeDuration, outValue, true); - assertEquals(TypedValue.TYPE_INT_DEC, outValue.type); - assertEquals(250, outValue.data); - - outValue = new TypedValue(); - mContext.resolveThemeAttribute(android.R.attr.scrollbarThumbHorizontal, outValue, true); - assertEquals(TypedValue.TYPE_STRING, outValue.type); - assertNotNull(outValue.string); - assertTrue(sRenderMessages.isEmpty()); - } - - @Test public void testColorStateList() throws Exception { final String STATE_LIST = "<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n" + @@ -2130,4 +2072,70 @@ public class RenderTests extends RenderTestBase { renderAndVerify(params, "html.png", TimeUnit.SECONDS.toNanos(2)); } + + @Test + public void testStatusBar() throws ClassNotFoundException { + final String layout = + "<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + + " android:layout_width=\"match_parent\"\n" + + " android:layout_height=\"match_parent\">\n" + "\n" + + " <TextView\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:text=\"Test status bar colour\"\n" + + " android:textSize=\"30sp\"/>\n" + + "</FrameLayout>"; + // Create LayoutLibCallback. + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); + layoutLibCallback.initResources(); + + SessionParams params = getSessionParamsBuilder() + .setParser(LayoutPullParser.createFromString(layout)) + .setCallback(layoutLibCallback) + .setTheme("DarkStatusBarTheme", true) + .setRenderingMode(RenderingMode.V_SCROLL) + .build(); + + renderAndVerify(params, "dark_status_bar.png", TimeUnit.SECONDS.toNanos(2)); + + params = getSessionParamsBuilder() + .setParser(LayoutPullParser.createFromString(layout)) + .setCallback(layoutLibCallback) + .setTheme("LightStatusBarTheme", true) + .setRenderingMode(RenderingMode.V_SCROLL) + .build(); + + renderAndVerify(params, "light_status_bar.png", TimeUnit.SECONDS.toNanos(2)); + } + + @Test + public void testSoftwareLayer() throws Exception { + String layout = + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + + " android:padding=\"16dp\"\n" + + " android:orientation=\"horizontal\"\n" + + " android:layout_width=\"fill_parent\"\n" + + " android:layout_height=\"fill_parent\">\n" + + " <com.android.layoutlib.test.myapplication.widgets.SoftwareTextView\n" + + " android:layout_height=\"200dp\"\n" + + " android:layout_width=\"wrap_content\" />\n" + + "</LinearLayout>\n"; + LayoutPullParser parser = LayoutPullParser.createFromString(layout); + // Create LayoutLibCallback. + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); + layoutLibCallback.initResources(); + + SessionParams params = getSessionParamsBuilder() + .setParser(parser) + .setCallback(layoutLibCallback) + .setTheme("Theme.Material.Light.NoActionBar.Fullscreen", false) + .setRenderingMode(RenderingMode.V_SCROLL) + .disableDecoration() + .build(); + + renderAndVerify(params, "software_layer.png", + TimeUnit.SECONDS.toNanos(2)); + } } diff --git a/bridge/tests/src/com/android/tools/idea/validator/LayoutValidatorTests.java b/bridge/tests/src/com/android/tools/idea/validator/LayoutValidatorTests.java index fd5e7f1780..44b42aef4c 100644 --- a/bridge/tests/src/com/android/tools/idea/validator/LayoutValidatorTests.java +++ b/bridge/tests/src/com/android/tools/idea/validator/LayoutValidatorTests.java @@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.android.RenderTestBase; import com.android.layoutlib.bridge.intensive.LayoutLibTestCallback; import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator; import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser; +import com.android.layoutlib.common.util.ReflectionUtils; import com.android.tools.idea.validator.ValidatorData.CompoundFix; import com.android.tools.idea.validator.ValidatorData.Issue; import com.android.tools.idea.validator.ValidatorData.Level; @@ -29,7 +30,9 @@ import com.android.tools.idea.validator.ValidatorData.Type; import org.junit.Test; +import android.util.SparseArray; import android.view.View; +import android.view.accessibility.AccessibilityInteractionClient; import java.util.ArrayList; import java.util.EnumSet; @@ -73,6 +76,9 @@ public class LayoutValidatorTests extends RenderTestBase { .build(); renderAndVerify(params, "a11y_test1.png"); + Object connectionCache = ReflectionUtils.getFieldValue(AccessibilityInteractionClient.class, + AccessibilityInteractionClient.getInstance(), "sConnectionCache"); + assertEquals(0, ((SparseArray)connectionCache).size()); } @Test @@ -83,6 +89,8 @@ public class LayoutValidatorTests extends RenderTestBase { null, SCALE_X_FOR_NEXUS_5, SCALE_Y_FOR_NEXUS_5); + assertEquals(4, result.getSrcMap().size()); + assertEquals(4, result.getNodeInfoMap().size()); assertEquals(31, result.getIssues().size()); ArrayList<Issue> errorIssues = new ArrayList<>(); for (Issue issue : result.getIssues()) { diff --git a/bridge/tests/src/com/android/tools/idea/validator/ValidatorResultTests.java b/bridge/tests/src/com/android/tools/idea/validator/ValidatorResultTests.java index 618200c921..b7e0e135f4 100644 --- a/bridge/tests/src/com/android/tools/idea/validator/ValidatorResultTests.java +++ b/bridge/tests/src/com/android/tools/idea/validator/ValidatorResultTests.java @@ -39,6 +39,7 @@ public class ValidatorResultTests { assertNotNull(result); assertTrue(result.getIssues().isEmpty()); assertTrue(result.getSrcMap().isEmpty()); + assertTrue(result.getNodeInfoMap().isEmpty()); assertNotNull(result.getMetric()); assertEquals("Result containing 0 issues:\n", result.toString()); } diff --git a/common/src/com/android/tools/layoutlib/create/NativeConfig.java b/common/src/com/android/tools/layoutlib/create/NativeConfig.java index 60a65efd95..8c954ce3dc 100644 --- a/common/src/com/android/tools/layoutlib/create/NativeConfig.java +++ b/common/src/com/android/tools/layoutlib/create/NativeConfig.java @@ -137,6 +137,8 @@ public class NativeConfig { "android.view.View#measure", "android.view.ViewRootImpl#performHapticFeedback", "android.view.WindowManagerGlobal#getWindowManagerService", + "android.view.accessibility.AccessibilityManager#getInstance", + "android.view.accessibility.AccessibilityManager#getWindowTransformationSpec", "android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow", "android.view.inputmethod.InputMethodManager#isInEditMode", "android.view.inputmethod.InputMethodManager#showSoftInput", diff --git a/create/Android.bp b/create/Android.bp index 13a00a878f..a8dcdaa5eb 100644 --- a/create/Android.bp +++ b/create/Android.bp @@ -30,7 +30,7 @@ java_binary_host { "guava", "layoutlib-common", "layoutlib_create-classpath", - "atf-prebuilt-502584086", + "atf-prebuilt-557133692", "libprotobuf-java-lite", ], } diff --git a/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/create/src/com/android/tools/layoutlib/create/AsmGenerator.java index 98055e3a90..55de0afe0f 100644 --- a/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +++ b/create/src/com/android/tools/layoutlib/create/AsmGenerator.java @@ -94,6 +94,8 @@ public class AsmGenerator { private final Set<MethodReplacer> mMethodReplacers; private boolean mKeepAllNativeClasses; + /** A map { FQCN => set { field names } } which should have their final modifier removed */ + private final Map<String, Set<String>> mRemoveFinalModifierFields; /** * Creates a new generator that can generate the output JAR with the stubbed classes. @@ -218,6 +220,9 @@ public class AsmGenerator { mRenameStaticInitializerClasses = Arrays.stream(createInfo.getDeferredStaticInitializerClasses()).collect(Collectors.toSet()); + + mRemoveFinalModifierFields = new HashMap<>(); + addToMap(createInfo.getRemovedFinalModifierFields(), mRemoveFinalModifierFields); } /** @@ -427,6 +432,11 @@ public class AsmGenerator { cv = new DeferStaticInitializerClassAdapter(cv); } + Set<String> removeFinalModifierFields = mRemoveFinalModifierFields.get(className); + if (removeFinalModifierFields != null && !removeFinalModifierFields.isEmpty()) { + cv = new RemoveFinalModifierFieldClassAdapter(cv, removeFinalModifierFields); + } + // Make sure no class file has a version above 55 (corresponding to Java 11), // so that layoutlib can be run with JDK 11. cv = new ChangeFileVersionAdapter(mLog, 55, cv); diff --git a/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/create/src/com/android/tools/layoutlib/create/CreateInfo.java index fb50c46ff1..b2b7e7084b 100644 --- a/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -129,6 +129,11 @@ public final class CreateInfo implements ICreateInfo { return DEFERRED_STATIC_INITIALIZER_CLASSES; } + @Override + public String[] getRemovedFinalModifierFields() { + return REMOVED_FINAL_MODIFIER_FIELDS; + } + //----- private static final MethodReplacer[] METHOD_REPLACERS = new MethodReplacer[] { @@ -381,6 +386,12 @@ public final class CreateInfo implements ICreateInfo { private final static Map<String, InjectMethodRunnable> INJECTED_METHODS = Map.of( "android.content.Context", InjectMethodRunnables.CONTEXT_GET_FRAMEWORK_CLASS_LOADER); + /** + * List of fields for which we will remove the final modifier. + */ + private final static String[] REMOVED_FINAL_MODIFIER_FIELDS = + new String[]{"android.animation.AnimationHandler#sAnimatorHandler"}; + public static class LinkedHashMapEldestReplacer implements MethodReplacer { private final String VOID_TO_MAP_ENTRY = diff --git a/create/src/com/android/tools/layoutlib/create/DeferStaticInitializerClassAdapter.java b/create/src/com/android/tools/layoutlib/create/DeferStaticInitializerClassAdapter.java index b5c331d838..3ce901a10b 100644 --- a/create/src/com/android/tools/layoutlib/create/DeferStaticInitializerClassAdapter.java +++ b/create/src/com/android/tools/layoutlib/create/DeferStaticInitializerClassAdapter.java @@ -47,7 +47,7 @@ public class DeferStaticInitializerClassAdapter extends ClassVisitor { // Java 9 does not allow static final field to be modified outside of <clinit>. // So if a field is static, it has to be non-final. if ((access & Opcodes.ACC_STATIC) != 0 ) { - access = access & ~Opcodes.ACC_FINAL;; + access = access & ~Opcodes.ACC_FINAL; } return super.visitField(access, name, desc, signature, value); } diff --git a/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/create/src/com/android/tools/layoutlib/create/ICreateInfo.java index 83c5b24523..e536bdc903 100644 --- a/create/src/com/android/tools/layoutlib/create/ICreateInfo.java +++ b/create/src/com/android/tools/layoutlib/create/ICreateInfo.java @@ -129,6 +129,13 @@ public interface ICreateInfo { String[] getDeferredStaticInitializerClasses(); + /** + * Returns a list of fields which should have their final modifier removed. + * The array values are in the form of the binary FQCN of the class containing the field and + * the field name separated by a '#'. + */ + String[] getRemovedFinalModifierFields(); + interface MethodReplacer { boolean isNeeded(String owner, String name, String desc, String sourceClass); diff --git a/create/src/com/android/tools/layoutlib/create/RemoveFinalModifierFieldClassAdapter.java b/create/src/com/android/tools/layoutlib/create/RemoveFinalModifierFieldClassAdapter.java new file mode 100644 index 0000000000..2f2d6007c7 --- /dev/null +++ b/create/src/com/android/tools/layoutlib/create/RemoveFinalModifierFieldClassAdapter.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 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.tools.layoutlib.create; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; + +import java.util.Set; + +import static org.objectweb.asm.Opcodes.ACC_FINAL; + +/** + * Removes the final modifier from the given fields. + */ +public class RemoveFinalModifierFieldClassAdapter extends ClassVisitor { + + private final Set<String> mFieldNames; + + public RemoveFinalModifierFieldClassAdapter(ClassVisitor cv, Set<String> fieldNames) { + super(Main.ASM_VERSION, cv); + mFieldNames = fieldNames; + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, + Object value) { + if (mFieldNames.contains(name)) { + access = access & ~ACC_FINAL; + } + return super.visitField(access, name, desc, signature, value); + } +} diff --git a/create/src/com/android/tools/layoutlib/create/StubExceptionMethodAdapter.java b/create/src/com/android/tools/layoutlib/create/StubExceptionMethodAdapter.java index 73a57a2190..322f0656a3 100644 --- a/create/src/com/android/tools/layoutlib/create/StubExceptionMethodAdapter.java +++ b/create/src/com/android/tools/layoutlib/create/StubExceptionMethodAdapter.java @@ -24,7 +24,7 @@ import org.objectweb.asm.Type; /** - * {@link MethodVisitor} that replaces the method the implementation of the method with + * {@link MethodVisitor} that replaces the implementation of the method with * <code> * throw new RuntimeException("Stub!"); * </code> diff --git a/create/tests/src/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/create/tests/src/com/android/tools/layoutlib/create/AsmGeneratorTest.java index aadab22173..334bcd2aa1 100644 --- a/create/tests/src/com/android/tools/layoutlib/create/AsmGeneratorTest.java +++ b/create/tests/src/com/android/tools/layoutlib/create/AsmGeneratorTest.java @@ -64,7 +64,7 @@ public class AsmGeneratorTest { private String mOsDestJar; private File mTempFile; - // ASM internal name for the the class in java package that should be refactored. + // ASM internal name for the class in java package that should be refactored. private static final String JAVA_CLASS_NAME = "notjava.lang.JavaClass"; @Before diff --git a/create/tests/src/com/android/tools/layoutlib/create/CreateInfoAdapter.java b/create/tests/src/com/android/tools/layoutlib/create/CreateInfoAdapter.java index 3b1cb06a1f..778a191aaf 100644 --- a/create/tests/src/com/android/tools/layoutlib/create/CreateInfoAdapter.java +++ b/create/tests/src/com/android/tools/layoutlib/create/CreateInfoAdapter.java @@ -106,4 +106,9 @@ class CreateInfoAdapter implements ICreateInfo { public String[] getDeferredStaticInitializerClasses() { return EMPTY_STRING_ARRAY; } + + @Override + public String[] getRemovedFinalModifierFields() { + return EMPTY_STRING_ARRAY; + } } diff --git a/create/tests/src/com/android/tools/layoutlib/create/PromoteClassClassAdapterTest.java b/create/tests/src/com/android/tools/layoutlib/create/PromoteClassClassAdapterTest.java index 3655ec23b7..0fa7ecb764 100644 --- a/create/tests/src/com/android/tools/layoutlib/create/PromoteClassClassAdapterTest.java +++ b/create/tests/src/com/android/tools/layoutlib/create/PromoteClassClassAdapterTest.java @@ -155,7 +155,7 @@ public class PromoteClassClassAdapterTest { PromoteClassClassAdapter adapter = new PromoteClassClassAdapter(log, Set.of( PackageProtectedClass.class.getName())); reader.accept(adapter, 0); - assertTrue(log.mLog.contains("[visit] - version=61, access=[public], " + + assertTrue(log.mLog.contains("[visit] - version=55, access=[public], " + "name=com/android/tools/layoutlib/create/PackageProtectedClass, signature=null, " + "superName=java/lang/Object, interfaces=[]")); diff --git a/split_universal_binary.sh b/split_universal_binary.sh index bd57245a7a..55b5b3829b 100755 --- a/split_universal_binary.sh +++ b/split_universal_binary.sh @@ -26,6 +26,7 @@ done # Put the single architecture binaries inside the DIST folder to be accessible on ab/ if [[ -d "${DIST_DIR}" ]]; then + mkdir -p ${DIST_DIR}/layoutlib_native/darwin cp -r ${OUT_DIR}/${ARM} ${DIST_DIR}/layoutlib_native/darwin cp -r ${OUT_DIR}/${X86} ${DIST_DIR}/layoutlib_native/darwin fi diff --git a/validator/resources/strings.properties b/validator/resources/strings.properties index 27f310cfa3..3a07104c0c 100644 --- a/validator/resources/strings.properties +++ b/validator/resources/strings.properties @@ -16,6 +16,7 @@ # result_message_not_important_for_accessibility = This item was not found to be important for accessibility. result_message_no_content_desc = This item has no <tt>android:contentDescription</tt>. +result_message_no_content_desc_generic = This item has no content description. result_message_not_visible = This item is not visible. result_message_not_enabled = This item isn\'t enabled. result_message_not_text_view = This item is not a <tt>TextView</tt>. @@ -38,6 +39,7 @@ non_clickable = non-clickable long_clickable = long clickable clickable_and_long_clickable = clickable and long clickable actionable = actionable +result_message_addendum_conflicting_elements_list = Conflicting element(s): <tt>%1$s</tt>. check_title_duplicate_speakable_text = Item descriptions result_message_brief_same_speakable_text = Multiple items have the same description. result_message_same_speakable_text = This %1$s item\'s speakable text: \"<tt>%2$s</tt>\" is identical to that of %3$d other item(s). @@ -55,10 +57,14 @@ result_message_image_customized_contrast_not_sufficient_confirmed = The image\'s check_title_redundant_description = Item type label result_message_english_locale_only = This check only runs on devices with locales set to English. result_message_brief_content_desc_contains_redundant_word = This item\'s <tt>android:contentDescription</tt> might contain unnecessary text. +result_message_brief_content_desc_contains_redundant_word_generic = This item\'s content description might contain unnecessary text. result_message_content_desc_ends_with_view_type = This item\'s <tt>android:contentDescription</tt>, \"<tt>%1$s</tt>\" ends with the item\'s type. result_message_content_desc_contains_redundant_word = This item\'s <tt>android:contentDescription</tt>, \"<tt>%1$s</tt>\" contains the item type \"<tt>%2$s</tt>\". +result_message_content_desc_contains_redundant_word_generic = This item\'s content description, \"<tt>%1$s</tt>\" contains the item type \"<tt>%2$s</tt>\". result_message_content_desc_contains_action = This item\'s <tt>android:contentDescription</tt>, \"<tt>%1$s</tt>\", contains the action \"<tt>%2$s</tt>\". +result_message_content_desc_contains_action_generic = This item\'s content description, \"<tt>%1$s</tt>\", contains the action \"<tt>%2$s</tt>\". result_message_content_desc_contains_state = This item\'s <tt>android:contentDescription</tt>, \"<tt>%1$s</tt>\", contains the state \"<tt>%2$s</tt>\". +result_message_content_desc_contains_state_generic = This item\'s content description, \"<tt>%1$s</tt>\", contains the state \"<tt>%2$s</tt>\". button_item_type = button checkbox_item_type = checkbox checkbox_item_type_separate_words = check box @@ -73,6 +79,7 @@ check_title_speakable_text_present = Item label result_message_should_not_focus = This item would not be focused by a screen reader. result_message_web_content = Web content is not evaluated. result_message_unsupported_compose_content = Composable content is not evaluated in this environment. +result_message_unsupported_flutter_content = Flutter content is not evaluated in this environment. result_message_missing_speakable_text = This item may not have a label readable by screen readers. check_title_text_contrast = Text contrast result_message_textview_empty = This <tt>TextView</tt> is empty. @@ -158,12 +165,19 @@ result_message_item_type_with_text_size_unit = <tt>%1$s</tt> with text size unit result_message_small_fixed_text_size = This text is small and may be difficult for some users to read. Consider using a larger size or specifying the text size in scaled pixels (<tt>sp</tt>). result_message_fixed_text_size = Consider specifying the text size in scaled pixels (<tt>sp</tt>). result_message_brief_fixed_width_text_view_with_scaled_text = This <tt>TextView</tt> has a fixed width and scalable text. +result_message_brief_fixed_width_text_view_with_scaled_text_compose = This <tt>Text</tt> has a fixed width and scalable text. result_message_brief_fixed_height_text_view_with_scaled_text = This <tt>TextView</tt> has a fixed height and scalable text. +result_message_brief_fixed_height_text_with_scaled_text_compose = This <tt>Text</tt> has a fixed height and scalable text. result_message_brief_fixed_size_text_view_with_scaled_text = This <tt>TextView</tt> has a fixed size and scalable text. +result_message_brief_fixed_size_text_with_scaled_text_compose = This <tt>Text</tt> has a fixed size and scalable text. result_message_brief_fixed_width_view_group_with_scaled_text = This <tt>ViewGroup</tt> has a fixed width and contains a <tt>TextView</tt> with scalable text. +result_message_brief_fixed_width_parent_with_scaled_text_compose = This element has a fixed width and contains a <tt>Text</tt> element with scalable text. result_message_brief_fixed_height_view_group_with_scaled_text = This <tt>ViewGroup</tt> has a fixed height and contains a <tt>TextView</tt> with scalable text. +result_message_brief_fixed_height_parent_with_scaled_text_compose = This element has a fixed height and contains a <tt>Text</tt> element with scalable text. result_message_brief_fixed_size_view_group_with_scaled_text = This <tt>ViewGroup</tt> has a fixed size and contains a <tt>TextView</tt> with scalable text. +result_message_brief_fixed_size_parent_with_scaled_text_compose = This element has a fixed size and contains a <tt>Text</tt> element with scalable text. result_message_fixed_size_text_view_with_scaled_text = Consider modifying the <tt>LayoutParams</tt> to allow for text expansion. +result_message_fixed_size_text_with_scaled_text_compose = Consider modifying the size modifiers using <tt>sizeIn</tt> to allow for text expansion. check_title_unexposed_text = Unexposed Text result_message_unexposed_text = Ensure this item's accessibility label includes its visible text. result_message_text_detected_in_image_view = OCR results were detected inside this ImageView. diff --git a/validator/src/com/android/tools/idea/validator/ValidatorResult.java b/validator/src/com/android/tools/idea/validator/ValidatorResult.java index 7f25d46489..9d708b5bdf 100644 --- a/validator/src/com/android/tools/idea/validator/ValidatorResult.java +++ b/validator/src/com/android/tools/idea/validator/ValidatorResult.java @@ -21,6 +21,7 @@ import com.android.tools.idea.validator.ValidatorData.Level; import com.android.tools.layoutlib.annotations.NotNull; import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; import java.util.ArrayList; import java.util.List; @@ -35,14 +36,21 @@ import com.google.common.collect.ImmutableBiMap; public class ValidatorResult { @NotNull private final ImmutableBiMap<Long, View> mSrcMap; + @NotNull private final ImmutableBiMap<Long, AccessibilityNodeInfo> mNodeInfoMap; @NotNull private final ArrayList<Issue> mIssues; @NotNull private final Metric mMetric; /** * Please use {@link Builder} for creating results. */ - private ValidatorResult(BiMap<Long, View> srcMap, ArrayList<Issue> issues, Metric metric) { + private ValidatorResult(BiMap<Long, View> srcMap, + BiMap<Long, AccessibilityNodeInfo> nodeInfoMap, + ArrayList<Issue> issues, + Metric metric) { mSrcMap = ImmutableBiMap.<Long, View>builder().putAll(srcMap).build(); + mNodeInfoMap = ImmutableBiMap.<Long, AccessibilityNodeInfo>builder() + .putAll(nodeInfoMap) + .build(); mIssues = issues; mMetric = metric; } @@ -55,6 +63,13 @@ public class ValidatorResult { } /** + * @return the map from source ID to AccessibilityNodeInfo. + */ + public ImmutableBiMap<Long, AccessibilityNodeInfo> getNodeInfoMap() { + return mNodeInfoMap; + } + + /** * @return list of issues. */ public List<Issue> getIssues() { @@ -89,11 +104,12 @@ public class ValidatorResult { public static class Builder { @NotNull public final BiMap<Long, View> mSrcMap = HashBiMap.create(); + @NotNull public final BiMap<Long, AccessibilityNodeInfo> mNodeInfoMap = HashBiMap.create(); @NotNull public final ArrayList<Issue> mIssues = new ArrayList<>(); @NotNull public final Metric mMetric = new Metric(); public ValidatorResult build() { - return new ValidatorResult(mSrcMap, mIssues, mMetric); + return new ValidatorResult(mSrcMap, mNodeInfoMap, mIssues, mMetric); } } diff --git a/validator/src/com/android/tools/idea/validator/ValidatorUtil.java b/validator/src/com/android/tools/idea/validator/ValidatorUtil.java index 6078046de5..fa2862d191 100644 --- a/validator/src/com/android/tools/idea/validator/ValidatorUtil.java +++ b/validator/src/com/android/tools/idea/validator/ValidatorUtil.java @@ -83,8 +83,6 @@ public class ValidatorUtil { * uses be redirected. */ StringManager.setResourceBundleProvider(locale -> ResourceBundle.getBundle("strings")); - // Enable using AccessibilityNodeInfo in addition to View for accessibility testing - AccessibilityHierarchyAndroid.viewOverlayEnabled = true; } // Visible for testing. @@ -130,7 +128,9 @@ public class ValidatorUtil { try { hierarchy.mView = AccessibilityHierarchyAndroid .newBuilder(view) + .enableViewOverlay() .setViewOriginMap(builder.mSrcMap) + .setNodeInfoOriginMap(builder.mNodeInfoMap) .setObtainCharacterLocations(LayoutValidator.obtainCharacterLocations()) .setCharacterLocationArgMaxLength(CHARACTER_LOCATION_ARG_MAX_LENGTH) .setCustomViewBuilder(new CustomViewBuilderAndroid() { diff --git a/validator/src/com/android/tools/idea/validator/hierarchy/CustomHierarchyHelper.java b/validator/src/com/android/tools/idea/validator/hierarchy/CustomHierarchyHelper.java index eee2f32c36..5c8b2e9a39 100644 --- a/validator/src/com/android/tools/idea/validator/hierarchy/CustomHierarchyHelper.java +++ b/validator/src/com/android/tools/idea/validator/hierarchy/CustomHierarchyHelper.java @@ -48,13 +48,29 @@ public class CustomHierarchyHelper { // This is required as layoutlib does not know the support library such as // MaterialButton. LayoutlibCallback calls for studio which understands all the maven // pulled library. - Class button = callback.findClass( + Class<?> button = callback.findClass( "com.google.android.material.button.MaterialButton"); if (button.isInstance(fromView)) { Method isCheckable = button.getMethod("isCheckable"); Object toReturn = isCheckable.invoke(fromView); return (toReturn instanceof Boolean) && ((Boolean) toReturn); } + + Class<?> card = callback.findClass( + "com.google.android.material.card.MaterialCardView"); + if (card.isInstance(fromView)) { + Method isCheckable = card.getMethod("isCheckable"); + Object toReturn = isCheckable.invoke(fromView); + return (toReturn instanceof Boolean) && ((Boolean) toReturn); + } + + Class<?> chip = callback.findClass( + "com.google.android.material.chip.Chip"); + if (chip.isInstance(fromView)) { + Method isCheckable = chip.getMethod("isCheckable"); + Object toReturn = isCheckable.invoke(fromView); + return (toReturn instanceof Boolean) && ((Boolean) toReturn); + } } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | |