diff options
author | Xin Li <delphij@google.com> | 2024-03-06 09:29:58 -0800 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2024-03-06 09:29:58 -0800 |
commit | 079b068bc74a02f8f38fc9831337fe59827e7f48 (patch) | |
tree | 9a97c5f50f426283c00d26751ed5dd76afff7663 | |
parent | 9a90f5ac6830432e2641304629d9a84f29603a4c (diff) | |
parent | 7a8f5cc819db70e2645299a057c1c45a669904e6 (diff) | |
download | layoutlib-main.tar.gz |
Bug: 319669529
Merged-In: I26dce9d10300386d9f9d0a8f6024514eb2e40e90
Change-Id: I1898bd8427f593690406ff61b295dbb85f0c6295
95 files changed, 1081 insertions, 262 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/bridge_client/src/com/android/layoutlib/bridge/intensive/util/SessionParamsBuilder.java b/bridge/bridge_client/src/com/android/layoutlib/bridge/intensive/util/SessionParamsBuilder.java index 35ade3454c..c0e22d3619 100644 --- a/bridge/bridge_client/src/com/android/layoutlib/bridge/intensive/util/SessionParamsBuilder.java +++ b/bridge/bridge_client/src/com/android/layoutlib/bridge/intensive/util/SessionParamsBuilder.java @@ -56,6 +56,7 @@ public class SessionParamsBuilder { private LayoutlibCallback mLayoutlibCallback; private int mTargetSdk; private int mMinSdk = 0; + private int mSimulatedSdk = 0; private ILayoutLog mLayoutLog; private Map<SessionParams.Key, Object> mFlags = new HashMap<>(); private AssetRepository mAssetRepository = null; @@ -135,6 +136,12 @@ public class SessionParamsBuilder { } @NonNull + public SessionParamsBuilder setSimulatedSdk(int simulatedSdk) { + mSimulatedSdk = simulatedSdk; + return this; + } + + @NonNull public SessionParamsBuilder setLayoutLog(@NonNull ILayoutLog layoutLog) { mLayoutLog = layoutLog; return this; @@ -202,7 +209,7 @@ public class SessionParamsBuilder { SessionParams params = new SessionParams(mLayoutParser, mRenderingMode, mProjectKey /* for caching */, mConfigGenerator.getHardwareConfig(), resourceResolver, mLayoutlibCallback, - mMinSdk, mTargetSdk, mLayoutLog); + mMinSdk, mTargetSdk, mLayoutLog, mSimulatedSdk); params.setFlag(RenderParamsFlags.FLAG_ENABLE_LAYOUT_VALIDATOR, enableLayoutValidator); params.setFlag( RenderParamsFlags.FLAG_ENABLE_LAYOUT_VALIDATOR_IMAGE_CHECK, 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/permission/PermissionManager_Delegate.java b/bridge/src/android/permission/PermissionManager_Delegate.java index 1aad83062d..642b015a2b 100644 --- a/bridge/src/android/permission/PermissionManager_Delegate.java +++ b/bridge/src/android/permission/PermissionManager_Delegate.java @@ -23,7 +23,7 @@ import android.content.pm.PackageManager; public class PermissionManager_Delegate { @LayoutlibDelegate - public static int checkPermission(String permission, int pid, int uid) { + public static int checkPermission(String permission, int pid, int uid, int deviceId) { return PackageManager.PERMISSION_GRANTED; } } diff --git a/bridge/src/android/provider/Settings_Config_Delegate.java b/bridge/src/android/provider/Settings_Config_Delegate.java new file mode 100644 index 0000000000..ae802ddc2b --- /dev/null +++ b/bridge/src/android/provider/Settings_Config_Delegate.java @@ -0,0 +1,35 @@ +/* + * 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.provider; + +import com.android.layoutlib.bridge.impl.RenderAction; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.content.ContentResolver; + +/** + * Delegate that provides alternative implementation for methods in {@link Settings.Config} + * <p/> + * Through the layoutlib_create tool, selected methods of DeviceConfig have been replaced by + * calls to methods of the same name in this delegate class. + */ +public class Settings_Config_Delegate { + @LayoutlibDelegate + static ContentResolver getContentResolver() { + return RenderAction.getCurrentContext().getContentResolver(); + } +} 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..b8c8f0e060 100644 --- a/bridge/src/android/view/AttachInfo_Accessor.java +++ b/bridge/src/android/view/AttachInfo_Accessor.java @@ -37,6 +37,8 @@ public class AttachInfo_Accessor { info.mInTouchMode = false; // this is so that we can display selections. info.mHardwareAccelerated = false; info.mApplicationScale = 1.0f; + ViewRootImpl_Accessor.setChild(root, view); + view.assignParent(root); view.dispatchAttachedToWindow(info, 0); } @@ -46,7 +48,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/BridgeInflater.java b/bridge/src/android/view/BridgeInflater.java index ec8476dfb5..ad9a442da7 100644 --- a/bridge/src/android/view/BridgeInflater.java +++ b/bridge/src/android/view/BridgeInflater.java @@ -250,6 +250,9 @@ public final class BridgeInflater extends LayoutInflater { */ @Nullable private View createViewFromCustomInflater(@NotNull String name, @NotNull AttributeSet attrs) { + if (!mLayoutlibCallback.shouldUseCustomInflater()) { + return null; + } if (mCustomInflater == null) { Context context = getContext(); context = getBaseContext(context); diff --git a/bridge/src/android/view/DisplayEventReceiver_Delegate.java b/bridge/src/android/view/DisplayEventReceiver_Delegate.java index f1d4e65725..0ca568cd86 100644 --- a/bridge/src/android/view/DisplayEventReceiver_Delegate.java +++ b/bridge/src/android/view/DisplayEventReceiver_Delegate.java @@ -20,6 +20,7 @@ import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.os.MessageQueue; +import android.view.DisplayEventReceiver.VsyncEventData; import java.lang.ref.WeakReference; @@ -32,7 +33,8 @@ public class DisplayEventReceiver_Delegate { @LayoutlibDelegate /*package*/ static long nativeInit(WeakReference<DisplayEventReceiver> receiver, - MessageQueue messageQueue, int vsyncSource, int eventRegistration, long layerHandle) { + WeakReference<VsyncEventData> vsyncEventData, MessageQueue messageQueue, + int vsyncSource, int eventRegistration, long layerHandle) { return sManager.addNewDelegate(new DisplayEventReceiver_Delegate()); } diff --git a/bridge/src/android/view/LayoutInflater_Delegate.java b/bridge/src/android/view/LayoutInflater_Delegate.java index cb446e7610..51c413d47d 100644 --- a/bridge/src/android/view/LayoutInflater_Delegate.java +++ b/bridge/src/android/view/LayoutInflater_Delegate.java @@ -233,15 +233,4 @@ public class LayoutInflater_Delegate { LayoutInflater.consumeChildElements(parser); } - - @LayoutlibDelegate - /* package */ static void initPrecompiledViews(LayoutInflater thisInflater) { - initPrecompiledViews(thisInflater, false); - } - - @LayoutlibDelegate - /* package */ static void initPrecompiledViews(LayoutInflater thisInflater, - boolean enablePrecompiledViews) { - thisInflater.initPrecompiledViews_Original(enablePrecompiledViews); - } } diff --git a/bridge/src/android/view/SurfaceView.java b/bridge/src/android/view/SurfaceView.java index ebb2af4532..2c1d6747e7 100644 --- a/bridge/src/android/view/SurfaceView.java +++ b/bridge/src/android/view/SurfaceView.java @@ -18,11 +18,17 @@ package android.view; import com.android.layoutlib.bridge.MockView; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.Region; +import android.os.IBinder; import android.util.AttributeSet; +import android.view.SurfaceControl.Transaction; + +import java.util.function.Consumer; /** * Mock version of the SurfaceView. @@ -50,6 +56,11 @@ public class SurfaceView extends MockView { super(context, attrs, defStyleAttr, defStyleRes); } + public SurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, + int defStyleRes, boolean disableBackgroundLayer) { + super(context, attrs, defStyleAttr, defStyleRes); + } + public boolean gatherTransparentRegion(Region region) { return false; } @@ -60,6 +71,14 @@ public class SurfaceView extends MockView { public void setZOrderOnTop(boolean onTop) { } + public boolean isZOrderedOnTop() { + return false; + } + + public boolean setZOrderedOnTop(boolean onTop, boolean allowDynamicChange) { + return true; + } + public void setSecure(boolean isSecure) { } @@ -67,6 +86,60 @@ public class SurfaceView extends MockView { return mSurfaceHolder; } + public void setUseAlpha() { + } + + public void setEnableSurfaceClipping(boolean enabled) { + } + + public void setCornerRadius(float cornerRadius) { + } + + public float getCornerRadius() { + return 0; + } + + public void setSurfaceLifecycle(int lifecycleStrategy) { + } + + public String getName() { + return "MockSurfaceView"; + } + + public void requestUpdateSurfacePositionAndScale() { + } + + public @NonNull Rect getSurfaceRenderPosition() { + return new Rect(); + } + + public boolean isFixedSize() { + return true; + } + + public void setResizeBackgroundColor(int bgColor) { + } + + public void setResizeBackgroundColor(@NonNull SurfaceControl.Transaction t, int bgColor) { + } + + public SurfaceControl getSurfaceControl() { + return null; + } + + public @Nullable IBinder getHostToken() { + return null; + } + + public void setChildSurfacePackage(@NonNull SurfaceControlViewHost.SurfacePackage p) { + } + + public void syncNextFrame(Consumer<Transaction> t) { + } + + public void applyTransactionToFrame(@NonNull SurfaceControl.Transaction transaction) { + } + private SurfaceHolder mSurfaceHolder = new SurfaceHolder() { @Override diff --git a/bridge/src/android/view/ViewRootImpl_Accessor.java b/bridge/src/android/view/ViewRootImpl_Accessor.java index 81ffe2e324..d15952ad3d 100644 --- a/bridge/src/android/view/ViewRootImpl_Accessor.java +++ b/bridge/src/android/view/ViewRootImpl_Accessor.java @@ -26,8 +26,17 @@ public class ViewRootImpl_Accessor { public static void setChild(ViewRootImpl viewRoot, View child) { viewRoot.mView = child; - child.assignParent(viewRoot); - viewRoot.mWidth = child.getWidth(); - viewRoot.mHeight = child.getHeight(); + if (child != null) { + viewRoot.mWidth = child.getWidth(); + viewRoot.mHeight = child.getHeight(); + } else { + viewRoot.mWidth = -1; + viewRoot.mHeight = -1; + } + } + + public static void detachFromWindow(ViewRootImpl viewRoot) { + viewRoot.mAccessibilityInteractionConnectionManager.ensureNoConnection(); + viewRoot.mAccessibilityInteractionConnectionManager.ensureNoDirectConnection(); } } diff --git a/bridge/src/android/view/WindowManagerImpl.java b/bridge/src/android/view/WindowManagerImpl.java index eb1e22c736..285ca9e5e4 100644 --- a/bridge/src/android/view/WindowManagerImpl.java +++ b/bridge/src/android/view/WindowManagerImpl.java @@ -41,6 +41,8 @@ import com.android.internal.R; import com.android.internal.policy.DecorView; import com.android.layoutlib.bridge.Bridge; +import java.util.ArrayList; + public class WindowManagerImpl implements WindowManager { private final Context mContext; @@ -179,10 +181,12 @@ public class WindowManagerImpl implements WindowManager { } } mCurrentRootView.addView(arg0, frameLayoutParams); + ViewRootImpl_Accessor.setChild(mBaseRootView.getViewRootImpl(), arg0); } @Override public void removeView(View arg0) { + ViewRootImpl viewRootImpl = arg0.getViewRootImpl(); if (mCurrentRootView != null) { mCurrentRootView.removeView(arg0); if (mBaseRootView != null && mCurrentRootView.getChildCount() == 0) { @@ -190,6 +194,20 @@ public class WindowManagerImpl implements WindowManager { mCurrentRootView = null; } } + if (viewRootImpl != null && viewRootImpl.getView() == arg0) { + View newRoot = null; + if (mCurrentRootView != null && mCurrentRootView.getChildCount() > 0) { + ArrayList<View> childrenList = mCurrentRootView.buildOrderedChildList(); + newRoot = childrenList.get(childrenList.size() - 1); + } else if (mBaseRootView != null) { + View root = mBaseRootView; + while (root.getParent() instanceof View) { + root = (View)root.getParent(); + } + newRoot = root; + } + ViewRootImpl_Accessor.setChild(viewRootImpl, newRoot); + } } @Override diff --git a/bridge/src/android/view/accessibility/AccessibilityInteractionClient_Accessor.java b/bridge/src/android/view/accessibility/AccessibilityInteractionClient_Accessor.java new file mode 100644 index 0000000000..8c87c22e88 --- /dev/null +++ b/bridge/src/android/view/accessibility/AccessibilityInteractionClient_Accessor.java @@ -0,0 +1,27 @@ +/* + * 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; + +public class AccessibilityInteractionClient_Accessor { + public static void clearCaches() { + AccessibilityInteractionClient.sCaches.clear(); + AccessibilityInteractionClient.sClients.clear(); + AccessibilityInteractionClient.sConnectionCache.clear(); + AccessibilityInteractionClient.sScrollingWindows.clear(); + AccessibilityInteractionClient.sDirectConnectionCount = 0; + } +} 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..9bffc23eb3 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; @@ -170,7 +173,7 @@ public class BridgeContext extends Context { private final LayoutlibCallback mLayoutlibCallback; private final WindowManager mWindowManager; private final DisplayManager mDisplayManager; - private final AutofillManager mAutofillManager; + private AutofillManager mAutofillManager; private final ClipboardManager mClipboardManager; private final ActivityManager mActivityManager; private final ConnectivityManager mConnectivityManager; @@ -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 @@ -263,7 +268,6 @@ public class BridgeContext extends Context { mWindowManager = new WindowManagerImpl(this, mMetrics); mDisplayManager = new DisplayManager(this); - mAutofillManager = new AutofillManager(this, new Default()); mClipboardManager = new ClipboardManager(this, null); mActivityManager = ActivityManager_Accessor.getActivityManagerInstance(this); mConnectivityManager = new ConnectivityManager(this, null); @@ -462,9 +466,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 +608,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 @@ -673,6 +687,9 @@ public class BridgeContext extends Context { return InputMethodManager.forContext(this); case AUTOFILL_MANAGER_SERVICE: + if (mAutofillManager == null) { + mAutofillManager = new AutofillManager(this, new Default()); + } return mAutofillManager; case CLIPBOARD_SERVICE: @@ -690,6 +707,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: @@ -1525,7 +1548,7 @@ public class BridgeContext extends Context { @Override public ContentResolver getContentResolver() { if (mContentResolver == null) { - mContentResolver = new BridgeContentResolver(this); + mContentResolver = new BridgeContentResolver(getApplicationContext()); } return mContentResolver; } @@ -2289,4 +2312,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/android/BridgeThermalService.java b/bridge/src/com/android/layoutlib/bridge/android/BridgeThermalService.java index 5fe116ea0c..0122da1818 100644 --- a/bridge/src/com/android/layoutlib/bridge/android/BridgeThermalService.java +++ b/bridge/src/com/android/layoutlib/bridge/android/BridgeThermalService.java @@ -90,4 +90,9 @@ public class BridgeThermalService implements IThermalService { public float getThermalHeadroom(int forecastSeconds) { return Float.NaN; } + + @Override + public float[] getThermalHeadroomThresholds() { + return new float[]{}; + } } 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..68423337a0 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. */ @@ -57,7 +74,7 @@ public class StatusBar extends CustomBar { @SuppressWarnings("UnusedParameters") public StatusBar(Context context, AttributeSet attrs) { this((BridgeContext) context, - Density.getEnum(((BridgeContext) context).getMetrics().densityDpi), + Density.create(((BridgeContext) context).getMetrics().densityDpi), ((BridgeContext) context).getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL, (context.getApplicationInfo().flags & ApplicationInfo.FLAG_SUPPORTS_RTL) != 0, @@ -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/Layout.java b/bridge/src/com/android/layoutlib/bridge/impl/Layout.java index c8e5009888..1ca3b9c2cc 100644 --- a/bridge/src/com/android/layoutlib/bridge/impl/Layout.java +++ b/bridge/src/com/android/layoutlib/bridge/impl/Layout.java @@ -38,6 +38,8 @@ import com.android.resources.ScreenOrientation; import android.R.id; import android.annotation.NonNull; import android.graphics.Color; +import android.graphics.Point; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.DisplayMetrics; import android.util.TypedValue; @@ -196,6 +198,25 @@ class Layout extends FrameLayout { mBuilder = null; } + @Override + public boolean getChildVisibleRect(View child, Rect r, Point offset, boolean forceParentCheck) { + return r.intersect(0, 0, getWidth(), getHeight()); + } + + @Override + public boolean getGlobalVisibleRect(Rect r, Point globalOffset) { + int width = mRight - mLeft; + int height = mBottom - mTop; + if (width > 0 && height > 0) { + r.set(0, 0, width, height); + if (globalOffset != null) { + globalOffset.set(-mScrollX, -mScrollY); + } + return true; + } + return false; + } + @NonNull private static View createSysUiOverlay(@NonNull BridgeContext context) { SysUiOverlay overlay = new SysUiOverlay(context, 20, 10, 50, 40, 60); diff --git a/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java index c2430a8959..98b59565f7 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; @@ -42,6 +43,7 @@ import android.view.IWindowManagerImpl; import android.view.Surface; import android.view.ViewConfiguration_Accessor; import android.view.WindowManagerGlobal_Delegate; +import android.view.accessibility.AccessibilityInteractionClient_Accessor; import android.view.inputmethod.InputMethodManager_Accessor; import java.util.Collections; @@ -51,6 +53,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 +71,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 +108,7 @@ public abstract class RenderAction<T extends RenderParams> { */ protected RenderAction(T params) { mParams = params; + sSimulatedSdk = SDK_INT; } /** @@ -276,6 +286,7 @@ public abstract class RenderAction<T extends RenderParams> { ILayoutLog currentLog = mParams.getLog(); Bridge.setLog(currentLog); mContext.getRenderResources().setLogger(currentLog); + AnimationHandler.sAnimatorHandler = mContext.getAnimationHandlerThreadLocal(); } /** @@ -303,6 +314,7 @@ public abstract class RenderAction<T extends RenderParams> { ParserFactory.setParserFactory(null); PropertyValuesHolder_Accessor.clearClassCaches(); + AccessibilityInteractionClient_Accessor.clearCaches(); } public static BridgeContext getCurrentContext() { @@ -467,6 +479,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..9ed5d17531 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,10 @@ import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; +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; @@ -109,6 +111,10 @@ import static com.android.layoutlib.common.util.ReflectionUtils.isInstanceOf; public class RenderSessionImpl extends RenderAction<SessionParams> { private static final Canvas NOP_CANVAS = new NopCanvas(); + private static final String SIMULATED_SDK_TOO_HIGH = + String.format("The current rendering only supports APIs up to %d. You may encounter " + + "crashes if using with higher APIs. To avoid, you can set a lower API for " + + "your previews.", SDK_INT); // scene state private RenderSession mScene; @@ -198,7 +204,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 +316,13 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { SessionParams params = getParams(); BridgeContext context = getContext(); + int simulatedVersion = params.getSimulatedPlatformVersion(); + sSimulatedSdk = simulatedVersion > 0 ? simulatedVersion : SDK_INT; + if (sSimulatedSdk > SDK_INT) { + Bridge.getLog().fidelityWarning(ILayoutLog.TAG_UNSUPPORTED, SIMULATED_SDK_TOO_HIGH, + null, null, null); + } + if (Bridge.isLocaleRtl(params.getLocale())) { if (!params.isRtlSupported()) { Bridge.getLog().warning(ILayoutLog.TAG_RTL_NOT_ENABLED, @@ -362,14 +375,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { mViewRoot.getViewRootImpl().mTmpFrames.displayFrame.set(mViewRoot.getLeft(), mViewRoot.getTop(), mViewRoot.getRight(), mViewRoot.getBottom()); - ViewRootImpl rootImpl = AttachInfo_Accessor.getRootView(mViewRoot); - if (rootImpl != null) { - ViewRootImpl_Accessor.setChild(rootImpl, mViewRoot); - } - mSystemViewInfoList = - visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(), - false); + visitAllChildren(mViewRoot, 0, 0, params, false); return SUCCESS.createResult(); } catch (PostInflateException e) { @@ -480,6 +487,13 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { SessionParams params = getParams(); + int simulatedVersion = params.getSimulatedPlatformVersion(); + sSimulatedSdk = simulatedVersion > 0 ? simulatedVersion : SDK_INT; + if (sSimulatedSdk > SDK_INT) { + Bridge.getLog().fidelityWarning(ILayoutLog.TAG_UNSUPPORTED, SIMULATED_SDK_TOO_HIGH, + null, null, null); + } + try { if (mViewRoot == null) { return ERROR_NOT_INFLATED.createResult(); @@ -570,8 +584,12 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } mSystemViewInfoList = - visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(), - false); + visitAllChildren(mViewRoot, 0, 0, params, false); + + Consumer<BufferedImage> imageTransformation = getParams().getImageTransformation(); + if (imageTransformation != null) { + imageTransformation.accept(mImage); + } boolean enableLayoutValidation = Boolean.TRUE.equals(params.getFlag(RenderParamsFlags.FLAG_ENABLE_LAYOUT_VALIDATOR)); boolean enableLayoutValidationImageCheck = Boolean.TRUE.equals( @@ -853,15 +871,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 +899,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 +915,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 +924,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 +938,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 +999,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 @@ -1184,6 +1208,19 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } } + @Override + public void release() { + super.release(); + if (mViewRoot == null) { + return; + } + ViewRootImpl viewRootImpl = mViewRoot.getViewRootImpl(); + if (viewRootImpl == null) { + return; + } + ViewRootImpl_Accessor.detachFromWindow(viewRootImpl); + } + private void disposeImageSurface() { if (mCanvas != null) { mCanvas.release(); @@ -1198,12 +1235,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/ResourceHelper.java b/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java index 87bed3d967..358795f256 100644 --- a/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java +++ b/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java @@ -111,7 +111,7 @@ import static android.content.res.AssetManager.ACCESS_STREAMING; public final class ResourceHelper { private static final Key<Set<ResourceValue>> KEY_GET_DRAWABLE = Key.create("ResourceHelper.getDrawable"); - private static final Pattern sFloatPattern = Pattern.compile("(-?[0-9]*(?:\\.[0-9]+)?)(.*)"); + private static final Pattern sFloatPattern = Pattern.compile("(-?[0-9]*(?:\\.[0-9]*)?)(.*)"); private static final float[] sFloatOut = new float[1]; private static final TypedValue mValue = new TypedValue(); @@ -368,7 +368,7 @@ public final class ResourceHelper { if (value instanceof DensityBasedResourceValue) { density = ((DensityBasedResourceValue) value).getResourceDensity(); if (density == Density.NODPI || density == Density.ANYDPI) { - density = Density.getEnum(context.getConfiguration().densityDpi); + density = Density.create(context.getConfiguration().densityDpi); } } 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/src/dalvik/system/VMRuntime_Delegate.java b/bridge/src/dalvik/system/VMRuntime_Delegate.java index 2fe10154b8..23faf55518 100644 --- a/bridge/src/dalvik/system/VMRuntime_Delegate.java +++ b/bridge/src/dalvik/system/VMRuntime_Delegate.java @@ -36,4 +36,9 @@ public class VMRuntime_Delegate { /*package*/ static int getNotifyNativeInterval() { return VMRuntimeCommonHelper.getNotifyNativeInterval(); } + + @LayoutlibDelegate + public static boolean is64Bit(VMRuntime runtime) { + return true; + } } 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..7ac28b683a 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..50b5f26443 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/rtl_ltr.png b/bridge/tests/res/testApp/MyApplication/golden/rtl_ltr.png Binary files differindex 118e926796..fab98dc2d4 100644 --- a/bridge/tests/res/testApp/MyApplication/golden/rtl_ltr.png +++ b/bridge/tests/res/testApp/MyApplication/golden/rtl_ltr.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/content/res/BridgeTypedArrayTest.java b/bridge/tests/src/android/content/res/BridgeTypedArrayTest.java index 39759279a3..dba0f76ee8 100644 --- a/bridge/tests/src/android/content/res/BridgeTypedArrayTest.java +++ b/bridge/tests/src/android/content/res/BridgeTypedArrayTest.java @@ -52,6 +52,7 @@ public class BridgeTypedArrayTest { assertEquals(TYPE_STRING, BridgeTypedArray.getType("#notacolor")); assertEquals(TYPE_DIMENSION, BridgeTypedArray.getType("16dp")); assertEquals(TYPE_DIMENSION, BridgeTypedArray.getType(".16dp")); + assertEquals(TYPE_DIMENSION, BridgeTypedArray.getType("9999.dp")); assertEquals(TYPE_STRING, BridgeTypedArray.getType("16notaunit")); assertEquals(TYPE_INT_DEC, BridgeTypedArray.getType("98543")); assertEquals(TYPE_FLOAT, BridgeTypedArray.getType("43.364")); 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..e1cad4ee2d 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,8 @@ 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.SessionParams.RenderingMode; +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 +30,13 @@ import org.junit.BeforeClass; import org.junit.Test; import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityInteractionClient; 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; @@ -58,6 +64,7 @@ public class AccessibilityTest extends RenderTestBase { try { Result renderResult = session.render(50000); assertTrue(renderResult.isSuccess()); + assertEquals(0, AccessibilityInteractionClient.sConnectionCache.size()); View rootView = (View)session.getSystemRootViews().get(0).getViewObject(); AccessibilityNodeInfo rootNode = rootView.createAccessibilityNodeInfo(); assertNotNull(rootNode); @@ -71,4 +78,101 @@ 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(); + } + } + + @Test + public void testDialogAccessibility() 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.DialogView\n" + + " android:layout_height=\"wrap_content\"\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(); + RenderSession session = sBridge.createSession(params); + session.setElapsedFrameTimeNanos(1); + try { + Result renderResult = session.render(50000); + assertTrue(renderResult.isSuccess()); + assertEquals(0, AccessibilityInteractionClient.sConnectionCache.size()); + View rootView = + (View)((View) session.getSystemRootViews().get(1).getViewObject()).getParent(); + int[] counter = {0}; + session.execute(() -> { + AccessibilityNodeInfo rootNode = rootView.createAccessibilityNodeInfo(); + assertNotNull(rootNode); + rootNode.setQueryFromAppProcessEnabled(rootView, true); + traverseAccessibilityTree(rootNode, counter); + }); + assertEquals(0, AccessibilityInteractionClient.sConnectionCache.size()); + assertEquals(17, counter[0]); + } finally { + session.dispose(); + } + } + + private void traverseAccessibilityTree(AccessibilityNodeInfo node, int[] counter) { + int childrenSize = node.getChildCount(); + for (int i = 0; i < childrenSize; i++) { + AccessibilityNodeInfo child = node.getChild(i); + counter[0]++; + traverseAccessibilityTree(child, counter); + } + } } 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..c5ef0e0488 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; @@ -72,10 +70,10 @@ import java.io.PrintWriter; import java.lang.reflect.Field; import java.util.concurrent.TimeUnit; +import static android.os._Original_Build.VERSION.SDK_INT; 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 +972,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 +2073,106 @@ 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)); + } + + @Test + public void testHighSimulatedSdk() 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" + + " <TextView\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:text=\"This is a TextView\" />\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) + .setSimulatedSdk(SDK_INT + 1) + .disableDecoration() + .build(); + + render(sBridge, params, -1); + boolean hasApiError = sRenderMessages.removeIf(message -> message.equals(String.format( + "The current rendering only supports APIs up to %d. You may encounter crashes if " + + "using with higher APIs. To avoid, you can set a lower API for your " + + "previews.", SDK_INT))); + assertTrue(hasApiError); + } } 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..339ce786ee 100644 --- a/common/src/com/android/tools/layoutlib/create/NativeConfig.java +++ b/common/src/com/android/tools/layoutlib/create/NativeConfig.java @@ -109,6 +109,7 @@ public class NativeConfig { "android.provider.DeviceConfig#getLong", "android.provider.DeviceConfig#getProperty", "android.provider.DeviceConfig#getString", + "android.provider.Settings$Config#getContentResolver", "android.text.format.DateFormat#is24HourFormat", "android.util.Xml#newPullParser", "android.view.Choreographer#getFrameTimeNanos", @@ -120,7 +121,6 @@ public class NativeConfig { "android.view.DisplayEventReceiver#nativeGetDisplayEventReceiverFinalizer", "android.view.DisplayEventReceiver#nativeInit", "android.view.HandlerActionQueue#postDelayed", - "android.view.LayoutInflater#initPrecompiledViews", "android.view.LayoutInflater#parseInclude", "android.view.LayoutInflater#rInflate", "android.view.MenuInflater#registerMenu", @@ -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", @@ -146,6 +148,7 @@ public class NativeConfig { "com.android.internal.util.XmlUtils#convertValueToInt", "com.android.internal.view.menu.MenuBuilder#createNewMenuItem", "dalvik.system.VMRuntime#getNotifyNativeInterval", + "dalvik.system.VMRuntime#is64Bit", "dalvik.system.VMRuntime#newUnpaddedArray", "libcore.io.MemoryMappedFile#bigEndianIterator", "libcore.io.MemoryMappedFile#close", 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..fbfd66895f 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[] { @@ -331,6 +336,11 @@ public final class CreateInfo implements ICreateInfo { "android.view.Choreographer#mCallbackQueues", // required for tests only "android.view.Choreographer$CallbackQueue#mHead", // required for tests only "android.view.ViewRootImpl#mTmpFrames", + "android.view.accessibility.AccessibilityInteractionClient#sCaches", + "android.view.accessibility.AccessibilityInteractionClient#sClients", + "android.view.accessibility.AccessibilityInteractionClient#sConnectionCache", + "android.view.accessibility.AccessibilityInteractionClient#sDirectConnectionCount", + "android.view.accessibility.AccessibilityInteractionClient#sScrollingWindows", "com.android.internal.util.ArrayUtils#sCache", }; @@ -381,6 +391,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 | |