blob: 9ae3140d6ef7a6794062df0b6fb53e9209a4d46b [file] [log] [blame]
/*
* Copyright (C) 2008 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.bridge.android;
import com.android.SdkConstants;
import com.android.ide.common.rendering.api.AssetRepository;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.LayoutLog;
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.ResourceReference;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.ResourceValueImpl;
import com.android.ide.common.rendering.api.StyleResourceValue;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.BridgeConstants;
import com.android.layoutlib.bridge.android.view.WindowManagerImpl;
import com.android.layoutlib.bridge.impl.ParserFactory;
import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.layoutlib.bridge.impl.Stack;
import com.android.resources.ResourceType;
import com.android.util.Pair;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.SystemServiceRegistry;
import android.content.BroadcastReceiver;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.BridgeAssetManager;
import android.content.res.BridgeTypedArray;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.Resources_Delegate;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Looper;
import android.os.Parcel;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.BridgeInflater;
import android.view.Display;
import android.view.DisplayAdjustments;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.view.autofill.AutofillManager;
import android.view.autofill.IAutoFillManager.Default;
import android.view.inputmethod.InputMethodManager;
import android.view.textservice.TextServicesManager;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import static android.os._Original_Build.VERSION_CODES.JELLY_BEAN_MR1;
import static com.android.layoutlib.bridge.android.RenderParamsFlags.FLAG_KEY_APPLICATION_PACKAGE;
/**
* Custom implementation of Context/Activity to handle non compiled resources.
*/
@SuppressWarnings("deprecation") // For use of Pair.
public class BridgeContext extends Context {
private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat";
private static final Map<String, ResourceValue> FRAMEWORK_PATCHED_VALUES = new HashMap<>(2);
private static final Map<String, ResourceValue> FRAMEWORK_REPLACE_VALUES = new HashMap<>(3);
static {
FRAMEWORK_PATCHED_VALUES.put("animateFirstView",
new ResourceValueImpl(ResourceNamespace.ANDROID, ResourceType.BOOL,
"animateFirstView", "false"));
FRAMEWORK_PATCHED_VALUES.put("animateLayoutChanges",
new ResourceValueImpl(ResourceNamespace.ANDROID, ResourceType.BOOL,
"animateLayoutChanges", "false"));
FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionItemLayout",
new ResourceValueImpl(ResourceNamespace.ANDROID, ResourceType.LAYOUT,
"textEditSuggestionItemLayout", "text_edit_suggestion_item"));
FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionContainerLayout",
new ResourceValueImpl(ResourceNamespace.ANDROID, ResourceType.LAYOUT,
"textEditSuggestionContainerLayout", "text_edit_suggestion_container"));
FRAMEWORK_REPLACE_VALUES.put("textEditSuggestionHighlightStyle",
new ResourceValueImpl(ResourceNamespace.ANDROID, ResourceType.STYLE,
"textEditSuggestionHighlightStyle",
"TextAppearance.Holo.SuggestionHighlight"));
}
/** The map adds cookies to each view so that IDE can link xml tags to views. */
private final HashMap<View, Object> mViewKeyMap = new HashMap<>();
/**
* In some cases, when inflating an xml, some objects are created. Then later, the objects are
* converted to views. This map stores the mapping from objects to cookies which can then be
* used to populate the mViewKeyMap.
*/
private final HashMap<Object, Object> mViewKeyHelpMap = new HashMap<>();
private final BridgeAssetManager mAssets;
private final boolean mShadowsEnabled;
private final boolean mHighQualityShadows;
private Resources mSystemResources;
private final Object mProjectKey;
private final DisplayMetrics mMetrics;
private final RenderResources mRenderResources;
private final Configuration mConfig;
private final ApplicationInfo mApplicationInfo;
private final LayoutlibCallback mLayoutlibCallback;
private final WindowManager mWindowManager;
private final DisplayManager mDisplayManager;
private final AutofillManager mAutofillManager;
private final ClipboardManager mClipboardManager;
private final HashMap<View, Integer> mScrollYPos = new HashMap<>();
private final HashMap<View, Integer> mScrollXPos = new HashMap<>();
private Resources.Theme mTheme;
private final Map<Object, Map<ResourceReference, ResourceValue>> mDefaultPropMaps =
new IdentityHashMap<>();
private final Map<Object, ResourceReference> mDefaultStyleMap = new IdentityHashMap<>();
// cache for TypedArray generated from StyleResourceValue object
private TypedArrayCache mTypedArrayCache;
private BridgeInflater mBridgeInflater;
private BridgeContentResolver mContentResolver;
private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<>();
private SharedPreferences mSharedPreferences;
private ClassLoader mClassLoader;
private IBinder mBinder;
private PackageManager mPackageManager;
private Boolean mIsThemeAppCompat;
private final ResourceNamespace mAppCompatNamespace;
private final Map<Key<?>, Object> mUserData = new HashMap<>();
/**
* Some applications that target both pre API 17 and post API 17, set the newer attrs to
* reference the older ones. For example, android:paddingStart will resolve to
* android:paddingLeft. This way the apps need to only define paddingLeft at any other place.
* This a map from value to attribute name. Warning for missing references shouldn't be logged
* if value and attr name pair is the same as an entry in this map.
*/
private static Map<String, String> RTL_ATTRS = new HashMap<>(10);
static {
RTL_ATTRS.put("?android:attr/paddingLeft", "paddingStart");
RTL_ATTRS.put("?android:attr/paddingRight", "paddingEnd");
RTL_ATTRS.put("?android:attr/layout_marginLeft", "layout_marginStart");
RTL_ATTRS.put("?android:attr/layout_marginRight", "layout_marginEnd");
RTL_ATTRS.put("?android:attr/layout_toLeftOf", "layout_toStartOf");
RTL_ATTRS.put("?android:attr/layout_toRightOf", "layout_toEndOf");
RTL_ATTRS.put("?android:attr/layout_alignParentLeft", "layout_alignParentStart");
RTL_ATTRS.put("?android:attr/layout_alignParentRight", "layout_alignParentEnd");
RTL_ATTRS.put("?android:attr/drawableLeft", "drawableStart");
RTL_ATTRS.put("?android:attr/drawableRight", "drawableEnd");
}
/**
* @param projectKey An Object identifying the project. This is used for the cache mechanism.
* @param metrics the {@link DisplayMetrics}.
* @param renderResources the configured resources (both framework and projects) for this
* render.
* @param config the Configuration object for this render.
* @param targetSdkVersion the targetSdkVersion of the application.
*/
public BridgeContext(Object projectKey, @NonNull DisplayMetrics metrics,
@NonNull RenderResources renderResources,
@NonNull AssetRepository assets,
@NonNull LayoutlibCallback layoutlibCallback,
@NonNull Configuration config,
int targetSdkVersion,
boolean hasRtlSupport,
boolean shadowsEnabled,
boolean highQualityShadows) {
mProjectKey = projectKey;
mMetrics = metrics;
mLayoutlibCallback = layoutlibCallback;
mRenderResources = renderResources;
mConfig = config;
AssetManager systemAssetManager = AssetManager.getSystem();
if (systemAssetManager instanceof BridgeAssetManager) {
mAssets = (BridgeAssetManager) systemAssetManager;
} else {
throw new AssertionError("Creating BridgeContext without initializing Bridge");
}
mAssets.setAssetRepository(assets);
mApplicationInfo = new ApplicationInfo();
mApplicationInfo.targetSdkVersion = targetSdkVersion;
if (hasRtlSupport) {
mApplicationInfo.flags = mApplicationInfo.flags | ApplicationInfo.FLAG_SUPPORTS_RTL;
}
mWindowManager = new WindowManagerImpl(this, mMetrics);
mDisplayManager = new DisplayManager(this);
mAutofillManager = new AutofillManager(this, new Default());
mClipboardManager = new ClipboardManager(this, null);
if (mLayoutlibCallback.isResourceNamespacingRequired()) {
if (mLayoutlibCallback.hasAndroidXAppCompat()) {
mAppCompatNamespace = ResourceNamespace.APPCOMPAT;
} else {
mAppCompatNamespace = ResourceNamespace.APPCOMPAT_LEGACY;
}
} else {
mAppCompatNamespace = ResourceNamespace.RES_AUTO;
}
mShadowsEnabled = shadowsEnabled;
mHighQualityShadows = highQualityShadows;
}
/**
* Initializes the {@link Resources} singleton to be linked to this {@link Context}, its
* {@link DisplayMetrics}, {@link Configuration}, and {@link LayoutlibCallback}.
*
* @see #disposeResources()
*/
public void initResources() {
AssetManager assetManager = AssetManager.getSystem();
mSystemResources = Resources_Delegate.initSystem(
this,
assetManager,
mMetrics,
mConfig,
mLayoutlibCallback);
mTheme = mSystemResources.newTheme();
}
/**
* Disposes the {@link Resources} singleton and the AssetRepository inside BridgeAssetManager.
*/
public void disposeResources() {
Resources_Delegate.disposeSystem();
// The BridgeAssetManager pointed to by the mAssets field is a long-lived object, but
// the AssetRepository is not. To prevent it from leaking clear a reference to it from
// the BridgeAssetManager.
mAssets.releaseAssetRepository();
}
public void setBridgeInflater(BridgeInflater inflater) {
mBridgeInflater = inflater;
}
public void addViewKey(View view, Object viewKey) {
mViewKeyMap.put(view, viewKey);
}
public Object getViewKey(View view) {
return mViewKeyMap.get(view);
}
public void addCookie(Object o, Object cookie) {
mViewKeyHelpMap.put(o, cookie);
}
public Object getCookie(Object o) {
return mViewKeyHelpMap.get(o);
}
public Object getProjectKey() {
return mProjectKey;
}
public DisplayMetrics getMetrics() {
return mMetrics;
}
public LayoutlibCallback getLayoutlibCallback() {
return mLayoutlibCallback;
}
public RenderResources getRenderResources() {
return mRenderResources;
}
public Map<Object, Map<ResourceReference, ResourceValue>> getDefaultProperties() {
return mDefaultPropMaps;
}
public Map<Object, ResourceReference> getDefaultNamespacedStyles() {
return mDefaultStyleMap;
}
public Configuration getConfiguration() {
return mConfig;
}
/**
* Adds a parser to the stack.
* @param parser the parser to add.
*/
public void pushParser(BridgeXmlBlockParser parser) {
if (ParserFactory.LOG_PARSER) {
System.out.println("PUSH " + parser.getParser().toString());
}
mParserStack.push(parser);
}
/**
* Removes the parser at the top of the stack
*/
public void popParser() {
BridgeXmlBlockParser parser = mParserStack.pop();
if (ParserFactory.LOG_PARSER) {
System.out.println("POPD " + parser.getParser().toString());
}
}
/**
* Returns the current parser at the top the of the stack.
* @return a parser or null.
*/
private BridgeXmlBlockParser getCurrentParser() {
return mParserStack.peek();
}
/**
* Returns the previous parser.
* @return a parser or null if there isn't any previous parser
*/
public BridgeXmlBlockParser getPreviousParser() {
if (mParserStack.size() < 2) {
return null;
}
return mParserStack.get(mParserStack.size() - 2);
}
public boolean resolveThemeAttribute(int resId, TypedValue outValue, boolean resolveRefs) {
ResourceReference resourceInfo = Bridge.resolveResourceId(resId);
if (resourceInfo == null) {
resourceInfo = mLayoutlibCallback.resolveResourceId(resId);
}
if (resourceInfo == null || resourceInfo.getResourceType() != ResourceType.ATTR) {
return false;
}
ResourceValue value = mRenderResources.findItemInTheme(resourceInfo);
if (resolveRefs) {
value = mRenderResources.resolveResValue(value);
}
if (value == null) {
// unable to find the attribute.
return false;
}
// check if this is a style resource
if (value instanceof StyleResourceValue) {
// get the id that will represent this style.
outValue.resourceId = getDynamicIdByStyle((StyleResourceValue) value);
return true;
}
String stringValue = value.getValue();
if (!stringValue.isEmpty()) {
if (stringValue.charAt(0) == '#') {
outValue.data = ResourceHelper.getColor(stringValue);
switch (stringValue.length()) {
case 4:
outValue.type = TypedValue.TYPE_INT_COLOR_RGB4;
break;
case 5:
outValue.type = TypedValue.TYPE_INT_COLOR_ARGB4;
break;
case 7:
outValue.type = TypedValue.TYPE_INT_COLOR_RGB8;
break;
default:
outValue.type = TypedValue.TYPE_INT_COLOR_ARGB8;
}
}
else if (stringValue.charAt(0) == '@') {
outValue.type = TypedValue.TYPE_REFERENCE;
}
else if ("true".equals(stringValue) || "false".equals(stringValue)) {
outValue.type = TypedValue.TYPE_INT_BOOLEAN;
outValue.data = "true".equals(stringValue) ? 1 : 0;
}
}
int a = getResourceId(value.asReference(), 0 /*defValue*/);
if (a != 0) {
outValue.resourceId = a;
return true;
}
// If the value is not a valid reference, fallback to pass the value as a string.
outValue.string = stringValue;
return true;
}
public ResourceReference resolveId(int id) {
// first get the String related to this id in the framework
ResourceReference resourceInfo = Bridge.resolveResourceId(id);
if (resourceInfo != null) {
return resourceInfo;
}
// didn't find a match in the framework? look in the project.
if (mLayoutlibCallback != null) {
resourceInfo = mLayoutlibCallback.resolveResourceId(id);
if (resourceInfo != null) {
return resourceInfo;
}
}
return null;
}
public Pair<View, Boolean> inflateView(ResourceReference layout, ViewGroup parent,
@SuppressWarnings("SameParameterValue") boolean attachToRoot,
boolean skipCallbackParser) {
boolean isPlatformLayout = layout.getNamespace().equals(ResourceNamespace.ANDROID);
if (!isPlatformLayout && !skipCallbackParser) {
// check if the project callback can provide us with a custom parser.
ILayoutPullParser parser = null;
ResourceValue layoutValue = mRenderResources.getResolvedResource(layout);
if (layoutValue != null) {
parser = getLayoutlibCallback().getParser(layoutValue);
}
if (parser != null) {
BridgeXmlBlockParser blockParser =
new BridgeXmlBlockParser(parser, this, layout.getNamespace());
try {
pushParser(blockParser);
return Pair.of(
mBridgeInflater.inflate(blockParser, parent, attachToRoot),
Boolean.TRUE);
} finally {
popParser();
}
}
}
ResourceValue resValue = mRenderResources.getResolvedResource(layout);
if (resValue != null) {
String path = resValue.getValue();
// We need to create a pull parser around the layout XML file, and then
// give that to our XmlBlockParser.
try {
XmlPullParser parser = ParserFactory.create(path, true);
if (parser != null) {
// Set the layout ref to have correct view cookies.
mBridgeInflater.setResourceReference(layout);
BridgeXmlBlockParser blockParser =
new BridgeXmlBlockParser(parser, this, layout.getNamespace());
try {
pushParser(blockParser);
return Pair.of(mBridgeInflater.inflate(blockParser, parent, attachToRoot),
Boolean.FALSE);
} finally {
popParser();
}
} else {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
String.format("File %s is missing!", path), null, null);
}
} catch (XmlPullParserException e) {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
"Failed to parse file " + path, e, null, null /*data*/);
// we'll return null below.
} finally {
mBridgeInflater.setResourceReference(null);
}
} else {
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
String.format("Layout %s%s does not exist.", isPlatformLayout ? "android:" : "",
layout.getName()), null, null);
}
return Pair.of(null, Boolean.FALSE);
}
/**
* Returns whether the current selected theme is based on AppCompat
*/
public boolean isAppCompatTheme() {
// If a cached value exists, return it.
if (mIsThemeAppCompat != null) {
return mIsThemeAppCompat;
}
// Ideally, we should check if the corresponding activity extends
// android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
StyleResourceValue defaultTheme = mRenderResources.getDefaultTheme();
// We can't simply check for parent using resources.themeIsParentOf() since the
// inheritance structure isn't really what one would expect. The first common parent
// between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21).
boolean isThemeAppCompat = false;
for (int i = 0; i < 50; i++) {
if (defaultTheme == null) {
break;
}
// for loop ensures that we don't run into cyclic theme inheritance.
if (defaultTheme.getName().startsWith(PREFIX_THEME_APPCOMPAT)) {
isThemeAppCompat = true;
break;
}
defaultTheme = mRenderResources.getParent(defaultTheme);
}
mIsThemeAppCompat = isThemeAppCompat;
return isThemeAppCompat;
}
// ------------ Context methods
@Override
public Resources getResources() {
return mSystemResources;
}
@Override
public Theme getTheme() {
return mTheme;
}
@Override
public ClassLoader getClassLoader() {
// The documentation for this method states that it should return a class loader one can
// use to retrieve classes in this package. However, when called by LayoutInflater, we do
// not want the class loader to return app's custom views.
// This is so that the IDE can instantiate the custom views and also generate proper error
// messages in case of failure. This also enables the IDE to fallback to MockView in case
// there's an exception thrown when trying to inflate the custom view.
// To work around this issue, LayoutInflater is modified via LayoutLib Create tool to
// replace invocations of this method to a new method: getFrameworkClassLoader(). Also,
// the method is injected into Context. The implementation of getFrameworkClassLoader() is:
// "return getClass().getClassLoader();". This means that when LayoutInflater asks for
// the context ClassLoader, it gets only LayoutLib's ClassLoader which doesn't have
// access to the apps's custom views.
// This method can now return the right ClassLoader, which CustomViews can use to do the
// right thing.
if (mClassLoader == null) {
mClassLoader = new ClassLoader(getClass().getClassLoader()) {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
for (String prefix : BridgeInflater.getClassPrefixList()) {
if (name.startsWith(prefix)) {
// These are framework classes and should not be loaded from the app.
throw new ClassNotFoundException(name + " not found");
}
}
return BridgeContext.this.mLayoutlibCallback.findClass(name);
}
};
}
return mClassLoader;
}
@Override
public Object getSystemService(String service) {
switch (service) {
case LAYOUT_INFLATER_SERVICE:
return mBridgeInflater;
case TEXT_SERVICES_MANAGER_SERVICE:
// we need to return a valid service to avoid NPE
return TextServicesManager.getInstance();
case WINDOW_SERVICE:
return mWindowManager;
case POWER_SERVICE:
return new PowerManager(this, new BridgePowerManager(), new BridgeThermalService(),
new Handler());
case DISPLAY_SERVICE:
return mDisplayManager;
case ACCESSIBILITY_SERVICE:
return AccessibilityManager.getInstance(this);
case INPUT_METHOD_SERVICE: // needed by SearchView and Compose
return InputMethodManager.forContext(this);
case AUTOFILL_MANAGER_SERVICE:
return mAutofillManager;
case CLIPBOARD_SERVICE:
return mClipboardManager;
case AUDIO_SERVICE:
case TEXT_CLASSIFICATION_SERVICE:
case CONTENT_CAPTURE_MANAGER_SERVICE:
return null;
default:
assert false : "Unsupported Service: " + service;
}
return null;
}
@Override
public String getSystemServiceName(Class<?> serviceClass) {
return SystemServiceRegistry.getSystemServiceName(serviceClass);
}
/**
* Same as Context#obtainStyledAttributes. We do not override the base method to give the
* original Context the chance to override the theme when needed.
*/
@Nullable
public final BridgeTypedArray internalObtainStyledAttributes(int resId, int[] attrs)
throws Resources.NotFoundException {
StyleResourceValue style = null;
// get the StyleResourceValue based on the resId;
if (resId != 0) {
style = getStyleByDynamicId(resId);
if (style == null) {
// In some cases, style may not be a dynamic id, so we do a full search.
ResourceReference ref = resolveId(resId);
if (ref != null) {
style = mRenderResources.getStyle(ref);
}
}
if (style == null) {
Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
"Failed to find style with " + resId, null, null);
return null;
}
}
if (mTypedArrayCache == null) {
mTypedArrayCache = new TypedArrayCache();
}
List<StyleResourceValue> currentThemes = mRenderResources.getAllThemes();
Pair<BridgeTypedArray, Map<ResourceReference, ResourceValue>> typeArrayAndPropertiesPair =
mTypedArrayCache.get(attrs, currentThemes, resId);
if (typeArrayAndPropertiesPair == null) {
typeArrayAndPropertiesPair = createStyleBasedTypedArray(style, attrs);
mTypedArrayCache.put(attrs, currentThemes, resId, typeArrayAndPropertiesPair);
}
// Add value to defaultPropsMap if needed
if (typeArrayAndPropertiesPair.getSecond() != null) {
BridgeXmlBlockParser parser = getCurrentParser();
Object key = parser != null ? parser.getViewCookie() : null;
if (key != null) {
Map<ResourceReference, ResourceValue> defaultPropMap = mDefaultPropMaps.get(key);
if (defaultPropMap == null) {
defaultPropMap = typeArrayAndPropertiesPair.getSecond();
mDefaultPropMaps.put(key, defaultPropMap);
} else {
defaultPropMap.putAll(typeArrayAndPropertiesPair.getSecond());
}
}
}
return typeArrayAndPropertiesPair.getFirst();
}
/**
* Same as Context#obtainStyledAttributes. We do not override the base method to give the
* original Context the chance to override the theme when needed.
*/
@Nullable
public BridgeTypedArray internalObtainStyledAttributes(@Nullable AttributeSet set, int[] attrs,
int defStyleAttr, int defStyleRes) {
Map<ResourceReference, ResourceValue> defaultPropMap = null;
Object key = null;
ResourceNamespace currentFileNamespace;
ResourceNamespace.Resolver resolver;
// Hint: for XmlPullParser, attach source //DEVICE_SRC/dalvik/libcore/xml/src/java
if (set instanceof BridgeXmlBlockParser) {
BridgeXmlBlockParser parser;
parser = (BridgeXmlBlockParser)set;
key = parser.getViewCookie();
if (key != null) {
defaultPropMap = mDefaultPropMaps.computeIfAbsent(key, k -> new HashMap<>());
}
currentFileNamespace = parser.getFileResourceNamespace();
resolver = new XmlPullParserResolver(parser, mLayoutlibCallback.getImplicitNamespaces());
} else if (set instanceof BridgeLayoutParamsMapAttributes) {
// This is for temp layout params generated dynamically in MockView. The set contains
// hardcoded values and we don't need to worry about resolving them.
currentFileNamespace = ResourceNamespace.RES_AUTO;
resolver = Resolver.EMPTY_RESOLVER;
} else if (set != null) {
// really this should not be happening since its instantiated in Bridge
Bridge.getLog().error(LayoutLog.TAG_BROKEN,
"Parser is not a BridgeXmlBlockParser!", null, null);
return null;
} else {
// `set` is null, so there will be no values to resolve.
currentFileNamespace = ResourceNamespace.RES_AUTO;
resolver = Resolver.EMPTY_RESOLVER;
}
List<AttributeHolder> attributeList = searchAttrs(attrs);
BridgeTypedArray ta =
Resources_Delegate.newTypeArray(mSystemResources, attrs.length);
// Look for a custom style.
StyleResourceValue customStyleValues = null;
if (set != null) {
String customStyle = set.getAttributeValue(null, "style");
if (customStyle != null) {
ResourceValue resolved = mRenderResources.resolveResValue(
new UnresolvedResourceValue(customStyle, currentFileNamespace, resolver));
if (resolved instanceof StyleResourceValue) {
customStyleValues = (StyleResourceValue) resolved;
}
}
}
// resolve the defStyleAttr value into a StyleResourceValue
StyleResourceValue defStyleValues = null;
if (defStyleAttr != 0) {
// get the name from the int.
ResourceReference defStyleAttribute = searchAttr(defStyleAttr);
if (defStyleAttribute == null) {
// This should be rare. Happens trying to map R.style.foo to @style/foo fails.
// This will happen if the user explicitly used a non existing int value for
// defStyleAttr or there's something wrong with the project structure/build.
Bridge.getLog().error(LayoutLog.TAG_RESOURCES_RESOLVE,
"Failed to find the style corresponding to the id " + defStyleAttr, null,
null);
} else {
// look for the style in the current theme, and its parent:
ResourceValue item = mRenderResources.findItemInTheme(defStyleAttribute);
if (item != null) {
if (key != null) {
mDefaultStyleMap.put(key, defStyleAttribute);
}
// item is a reference to a style entry. Search for it.
item = mRenderResources.resolveResValue(item);
if (item instanceof StyleResourceValue) {
defStyleValues = (StyleResourceValue) item;
}
}
}
}
if (defStyleValues == null && defStyleRes != 0) {
StyleResourceValue item = getStyleByDynamicId(defStyleRes);
if (item != null) {
defStyleValues = item;
} else {
ResourceReference value = Bridge.resolveResourceId(defStyleRes);
if (value == null) {
value = mLayoutlibCallback.resolveResourceId(defStyleRes);
}
if (value != null) {
if ((value.getResourceType() == ResourceType.STYLE)) {
// look for the style in all resources:
item = mRenderResources.getStyle(value);
if (item != null) {
if (key != null) {
mDefaultStyleMap.put(key, item.asReference());
}
defStyleValues = item;
} else {
Bridge.getLog().error(null,
String.format(
"Style with id 0x%x (resolved to '%s') does not exist.",
defStyleRes, value.getName()),
null, null);
}
} else {
Bridge.getLog().error(null,
String.format(
"Resource id 0x%x is not of type STYLE (instead %s)",
defStyleRes, value.getResourceType().name()),
null, null);
}
} else {
Bridge.getLog().error(null,
String.format(
"Failed to find style with id 0x%x in current theme",
defStyleRes),
null, null);
}
}
}
if (attributeList != null) {
for (int index = 0 ; index < attributeList.size() ; index++) {
AttributeHolder attributeHolder = attributeList.get(index);
if (attributeHolder == null) {
continue;
}
String attrName = attributeHolder.getName();
String value = null;
if (set != null) {
value = set.getAttributeValue(
attributeHolder.getNamespace().getXmlNamespaceUri(), attrName);
// if this is an app attribute, and the first get fails, try with the
// new res-auto namespace as well
if (attributeHolder.getNamespace() != ResourceNamespace.ANDROID && value == null) {
value = set.getAttributeValue(BridgeConstants.NS_APP_RES_AUTO, attrName);
}
}
// Calculate the default value from the Theme in two cases:
// - If defaultPropMap is not null, get the default value to add it to the list
// of default values of properties.
// - If value is null, it means that the attribute is not directly set as an
// attribute in the XML so try to get the default value.
ResourceValue defaultValue = null;
if (defaultPropMap != null || value == null) {
// look for the value in the custom style first (and its parent if needed)
ResourceReference attrRef = attributeHolder.asReference();
if (customStyleValues != null) {
defaultValue =
mRenderResources.findItemInStyle(customStyleValues, attrRef);
}
// then look for the value in the default Style (and its parent if needed)
if (defaultValue == null && defStyleValues != null) {
defaultValue =
mRenderResources.findItemInStyle(defStyleValues, attrRef);
}
// if the item is not present in the defStyle, we look in the main theme (and
// its parent themes)
if (defaultValue == null) {
defaultValue =
mRenderResources.findItemInTheme(attrRef);
}
// if we found a value, we make sure this doesn't reference another value.
// So we resolve it.
if (defaultValue != null) {
if (defaultPropMap != null) {
defaultPropMap.put(attrRef, defaultValue);
}
defaultValue = mRenderResources.resolveResValue(defaultValue);
}
}
// Done calculating the defaultValue.
// If there's no direct value for this attribute in the XML, we look for default
// values in the widget defStyle, and then in the theme.
if (value == null) {
if (attributeHolder.getNamespace() == ResourceNamespace.ANDROID) {
// For some framework values, layoutlib patches the actual value in the
// theme when it helps to improve the final preview. In most cases
// we just disable animations.
ResourceValue patchedValue = FRAMEWORK_PATCHED_VALUES.get(attrName);
if (patchedValue != null) {
defaultValue = patchedValue;
}
}
// If we found a value, we make sure this doesn't reference another value.
// So we resolve it.
if (defaultValue != null) {
// If the value is a reference to another theme attribute that doesn't
// exist, we should log a warning and omit it.
String val = defaultValue.getValue();
if (val != null && val.startsWith(SdkConstants.PREFIX_THEME_REF)) {
// Because we always use the latest framework code, some resources might
// fail to resolve when using old themes (they haven't been backported).
// Since this is an artifact caused by us using always the latest
// code, we check for some of those values and replace them here.
ResourceReference reference = defaultValue.getReference();
defaultValue = FRAMEWORK_REPLACE_VALUES.get(attrName);
// Only log a warning if the referenced value isn't one of the RTL
// attributes, or the app targets old API.
if (defaultValue == null &&
(getApplicationInfo().targetSdkVersion < JELLY_BEAN_MR1 ||
!attrName.equals(RTL_ATTRS.get(val)))) {
if (reference != null) {
val = reference.getResourceUrl().toString();
}
Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR,
String.format("Failed to find '%s' in current theme.", val),
null, val);
}
}
}
ta.bridgeSetValue(
index,
attrName, attributeHolder.getNamespace(),
attributeHolder.getResourceId(),
defaultValue);
} else {
// There is a value in the XML, but we need to resolve it in case it's
// referencing another resource or a theme value.
ta.bridgeSetValue(
index,
attrName, attributeHolder.getNamespace(),
attributeHolder.getResourceId(),
mRenderResources.resolveResValue(
new UnresolvedResourceValue(
value, currentFileNamespace, resolver)));
}
}
}
ta.sealArray();
return ta;
}
@Override
public Looper getMainLooper() {
return Looper.myLooper();
}
@Override
public String getPackageName() {
if (mApplicationInfo.packageName == null) {
mApplicationInfo.packageName = mLayoutlibCallback.getFlag(FLAG_KEY_APPLICATION_PACKAGE);
}
return mApplicationInfo.packageName;
}
@Override
public PackageManager getPackageManager() {
if (mPackageManager == null) {
mPackageManager = new BridgePackageManager();
}
return mPackageManager;
}
// ------------- private new methods
/**
* Creates a {@link BridgeTypedArray} by filling the values defined by the int[] with the
* values found in the given style. If no style is specified, the default theme, along with the
* styles applied to it are used.
*
* @see #obtainStyledAttributes(int, int[])
*/
private Pair<BridgeTypedArray, Map<ResourceReference, ResourceValue>> createStyleBasedTypedArray(
@Nullable StyleResourceValue style, int[] attrs) throws Resources.NotFoundException {
List<AttributeHolder> attributes = searchAttrs(attrs);
BridgeTypedArray ta =
Resources_Delegate.newTypeArray(mSystemResources, attrs.length);
Map<ResourceReference, ResourceValue> defaultPropMap = new HashMap<>();
// for each attribute, get its name so that we can search it in the style
for (int i = 0; i < attrs.length; i++) {
AttributeHolder attrHolder = attributes.get(i);
if (attrHolder != null) {
// look for the value in the given style
ResourceValue resValue;
if (style != null) {
resValue = mRenderResources.findItemInStyle(style, attrHolder.asReference());
} else {
resValue = mRenderResources.findItemInTheme(attrHolder.asReference());
}
if (resValue != null) {
defaultPropMap.put(attrHolder.asReference(), resValue);
// resolve it to make sure there are no references left.
resValue = mRenderResources.resolveResValue(resValue);
ta.bridgeSetValue(
i, attrHolder.getName(), attrHolder.getNamespace(),
attrHolder.getResourceId(),
resValue);
}
}
}
ta.sealArray();
return Pair.of(ta, defaultPropMap);
}
/**
* The input int[] attributeIds is a list of attributes. The returns a list of information about
* each attributes. The information is (name, isFramework)
* <p/>
*
* @param attributeIds An attribute array reference given to obtainStyledAttributes.
* @return List of attribute information.
*/
private List<AttributeHolder> searchAttrs(int[] attributeIds) {
List<AttributeHolder> results = new ArrayList<>(attributeIds.length);
// for each attribute, get its name so that we can search it in the style
for (int id : attributeIds) {
ResourceReference refForId = Bridge.resolveResourceId(id);
if (refForId == null) {
refForId = mLayoutlibCallback.resolveResourceId(id);
}
if (refForId != null) {
results.add(new AttributeHolder(id, refForId));
} else {
results.add(null);
}
}
return results;
}
/**
* Searches for the attribute referenced by its internal id.
*/
private ResourceReference searchAttr(int attrId) {
ResourceReference attr = Bridge.resolveResourceId(attrId);
if (attr == null) {
attr = mLayoutlibCallback.resolveResourceId(attrId);
}
return attr;
}
/**
* Maps a given style to a numeric id.
*
* <p>For now Bridge handles numeric ids (both fixed and dynamic) for framework and the callback
* for non-framework. TODO(b/156609434): teach the IDE about fixed framework ids and handle this
* all in the callback.
*/
public int getDynamicIdByStyle(StyleResourceValue resValue) {
if (resValue.isFramework()) {
return Bridge.getResourceId(resValue.getResourceType(), resValue.getName());
} else {
return mLayoutlibCallback.getOrGenerateResourceId(resValue.asReference());
}
}
/**
* Maps a numeric id back to {@link StyleResourceValue}.
*
* <p>For now framework numeric ids are handled by Bridge, so try there first and fall back to
* the callback, which manages ids for non-framework resources. TODO(b/156609434): manage all
* ids in the IDE.
*
* <p>Once we the resource for the given id, we ask the IDE to get the
* {@link StyleResourceValue} for it.
*/
@Nullable
private StyleResourceValue getStyleByDynamicId(int id) {
ResourceReference reference = Bridge.resolveResourceId(id);
if (reference == null) {
reference = mLayoutlibCallback.resolveResourceId(id);
}
if (reference == null) {
return null;
}
return mRenderResources.getStyle(reference);
}
public int getResourceId(@NonNull ResourceReference resource, int defValue) {
if (getRenderResources().getUnresolvedResource(resource) != null) {
if (resource.getNamespace().equals(ResourceNamespace.ANDROID)) {
return Bridge.getResourceId(resource.getResourceType(), resource.getName());
} else if (mLayoutlibCallback != null) {
return mLayoutlibCallback.getOrGenerateResourceId(resource);
}
}
return defValue;
}
public static Context getBaseContext(Context context) {
while (context instanceof ContextWrapper) {
context = ((ContextWrapper) context).getBaseContext();
}
return context;
}
/**
* Returns the Framework attr resource reference with the given name.
*/
@NonNull
public static ResourceReference createFrameworkAttrReference(@NonNull String name) {
return createFrameworkResourceReference(ResourceType.ATTR, name);
}
/**
* Returns the Framework resource reference with the given type and name.
*/
@NonNull
public static ResourceReference createFrameworkResourceReference(@NonNull ResourceType type,
@NonNull String name) {
return new ResourceReference(ResourceNamespace.ANDROID, type, name);
}
/**
* Returns the AppCompat attr resource reference with the given name.
*/
@NonNull
public ResourceReference createAppCompatAttrReference(@NonNull String name) {
return createAppCompatResourceReference(ResourceType.ATTR, name);
}
/**
* Returns the AppCompat resource reference with the given type and name.
*/
@NonNull
public ResourceReference createAppCompatResourceReference(@NonNull ResourceType type,
@NonNull String name) {
return new ResourceReference(mAppCompatNamespace, type, name);
}
public IBinder getBinder() {
if (mBinder == null) {
// create a no-op binder. We only need it be not null.
mBinder = new IBinder() {
@Override
public String getInterfaceDescriptor() throws RemoteException {
return null;
}
@Override
public boolean pingBinder() {
return false;
}
@Override
public boolean isBinderAlive() {
return false;
}
@Override
public IInterface queryLocalInterface(String descriptor) {
return null;
}
@Override
public void dump(FileDescriptor fd, String[] args) throws RemoteException {
}
@Override
public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
}
@Override
public boolean transact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
return false;
}
@Override
public void linkToDeath(DeathRecipient recipient, int flags)
throws RemoteException {
}
@Override
public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
return false;
}
@Override
public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback shellCallback, ResultReceiver resultReceiver) {
}
};
}
return mBinder;
}
//------------ NOT OVERRIDEN --------------------
@Override
public boolean bindService(Intent arg0, ServiceConnection arg1, int arg2) {
// pass
return false;
}
@Override
public boolean bindService(Intent arg0, int arg1, Executor arg2, ServiceConnection arg3) {
return false;
}
@Override
public boolean bindIsolatedService(Intent arg0,
int arg1, String arg2, Executor arg3, ServiceConnection arg4) {
return false;
}
@Override
public int checkCallingOrSelfPermission(String arg0) {
// pass
return 0;
}
@Override
public int checkCallingOrSelfUriPermission(Uri arg0, int arg1) {
// pass
return 0;
}
@Override
public int checkCallingPermission(String arg0) {
// pass
return 0;
}
@Override
public int checkCallingUriPermission(Uri arg0, int arg1) {
// pass
return 0;
}
@Override
public int checkPermission(String arg0, int arg1, int arg2) {
// pass
return 0;
}
@Override
public int checkSelfPermission(String arg0) {
// pass
return 0;
}
@Override
public int checkPermission(String arg0, int arg1, int arg2, IBinder arg3) {
// pass
return 0;
}
@Override
public int checkUriPermission(Uri arg0, int arg1, int arg2, int arg3) {
// pass
return 0;
}
@Override
public int checkUriPermission(Uri arg0, int arg1, int arg2, int arg3, IBinder arg4) {
// pass
return 0;
}
@Override
public int checkUriPermission(Uri arg0, String arg1, String arg2, int arg3,
int arg4, int arg5) {
// pass
return 0;
}
@Override
public void clearWallpaper() {
// pass
}
@Override
public Context createPackageContext(String arg0, int arg1) {
// pass
return null;
}
@Override
public Context createPackageContextAsUser(String arg0, int arg1, UserHandle user) {
// pass
return null;
}
@Override
public Context createConfigurationContext(Configuration overrideConfiguration) {
// pass
return null;
}
@Override
public Context createDisplayContext(Display display) {
// pass
return null;
}
@Override
public Context createContextForSplit(String splitName) {
// pass
return null;
}
@Override
public String[] databaseList() {
// pass
return null;
}
@Override
public Context createApplicationContext(ApplicationInfo application, int flags)
throws PackageManager.NameNotFoundException {
return null;
}
@Override
public boolean moveDatabaseFrom(Context sourceContext, String name) {
// pass
return false;
}
@Override
public boolean deleteDatabase(String arg0) {
// pass
return false;
}
@Override
public boolean deleteFile(String arg0) {
// pass
return false;
}
@Override
public void enforceCallingOrSelfPermission(String arg0, String arg1) {
// pass
}
@Override
public void enforceCallingOrSelfUriPermission(Uri arg0, int arg1,
String arg2) {
// pass
}
@Override
public void enforceCallingPermission(String arg0, String arg1) {
// pass
}
@Override
public void enforceCallingUriPermission(Uri arg0, int arg1, String arg2) {
// pass
}
@Override
public void enforcePermission(String arg0, int arg1, int arg2, String arg3) {
// pass
}
@Override
public void enforceUriPermission(Uri arg0, int arg1, int arg2, int arg3,
String arg4) {
// pass
}
@Override
public void enforceUriPermission(Uri arg0, String arg1, String arg2,
int arg3, int arg4, int arg5, String arg6) {
// pass
}
@Override
public String[] fileList() {
// pass
return null;
}
@Override
public BridgeAssetManager getAssets() {
return mAssets;
}
@Override
public File getCacheDir() {
// pass
return null;
}
@Override
public File getCodeCacheDir() {
// pass
return null;
}
@Override
public File getExternalCacheDir() {
// pass
return null;
}
@Override
public File getPreloadsFileCache() {
// pass
return null;
}
@Override
public ContentResolver getContentResolver() {
if (mContentResolver == null) {
mContentResolver = new BridgeContentResolver(this);
}
return mContentResolver;
}
@Override
public File getDatabasePath(String arg0) {
// pass
return null;
}
@Override
public File getDir(String arg0, int arg1) {
// pass
return null;
}
@Override
public File getFileStreamPath(String arg0) {
// pass
return null;
}
@Override
public File getSharedPreferencesPath(String name) {
// pass
return null;
}
@Override
public File getDataDir() {
// pass
return null;
}
@Override
public File getFilesDir() {
// pass
return null;
}
@Override
public File getNoBackupFilesDir() {
// pass
return null;
}
@Override
public File getExternalFilesDir(String type) {
// pass
return null;
}
@Override
public String getPackageCodePath() {
// pass
return null;
}
@Override
public String getBasePackageName() {
// pass
return null;
}
@Override
public String getOpPackageName() {
// pass
return null;
}
@Override
public ApplicationInfo getApplicationInfo() {
return mApplicationInfo;
}
@Override
public String getPackageResourcePath() {
// pass
return null;
}
@Override
public SharedPreferences getSharedPreferences(String arg0, int arg1) {
if (mSharedPreferences == null) {
mSharedPreferences = new BridgeSharedPreferences();
}
return mSharedPreferences;
}
@Override
public SharedPreferences getSharedPreferences(File arg0, int arg1) {
if (mSharedPreferences == null) {
mSharedPreferences = new BridgeSharedPreferences();
}
return mSharedPreferences;
}
@Override
public void reloadSharedPreferences() {
// intentional noop
}
@Override
public boolean moveSharedPreferencesFrom(Context sourceContext, String name) {
// pass
return false;
}
@Override
public boolean deleteSharedPreferences(String name) {
// pass
return false;
}
@Override
public Drawable getWallpaper() {
// pass
return null;
}
@Override
public int getWallpaperDesiredMinimumWidth() {
return -1;
}
@Override
public int getWallpaperDesiredMinimumHeight() {
return -1;
}
@Override
public void grantUriPermission(String arg0, Uri arg1, int arg2) {
// pass
}
@Override
public FileInputStream openFileInput(String arg0) throws FileNotFoundException {
// pass
return null;
}
@Override
public FileOutputStream openFileOutput(String arg0, int arg1) throws FileNotFoundException {
// pass
return null;
}
@Override
public SQLiteDatabase openOrCreateDatabase(String arg0, int arg1, CursorFactory arg2) {
// pass
return null;
}
@Override
public SQLiteDatabase openOrCreateDatabase(String arg0, int arg1,
CursorFactory arg2, DatabaseErrorHandler arg3) {
// pass
return null;
}
@Override
public Drawable peekWallpaper() {
// pass
return null;
}
@Override
public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1) {
// pass
return null;
}
@Override
public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1, int arg2) {
// pass
return null;
}
@Override
public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1,
String arg2, Handler arg3) {
// pass
return null;
}
@Override
public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1,
String arg2, Handler arg3, int arg4) {
// pass
return null;
}
@Override
public Intent registerReceiverAsUser(BroadcastReceiver arg0, UserHandle arg0p5,
IntentFilter arg1, String arg2, Handler arg3) {
// pass
return null;
}
@Override
public Intent registerReceiverAsUser(BroadcastReceiver arg0, UserHandle arg0p5,
IntentFilter arg1, String arg2, Handler arg3, int arg4) {
// pass
return null;
}
@Override
public void removeStickyBroadcast(Intent arg0) {
// pass
}
@Override
public void revokeUriPermission(Uri arg0, int arg1) {
// pass
}
@Override
public void revokeUriPermission(String arg0, Uri arg1, int arg2) {
// pass
}
@Override
public void sendBroadcast(Intent arg0) {
// pass
}
@Override
public void sendBroadcast(Intent arg0, String arg1) {
// pass
}
@Override
public void sendBroadcastMultiplePermissions(Intent intent, String[] receiverPermissions) {
// pass
}
@Override
public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user,
String[] receiverPermissions) {
// pass
}
@Override
public void sendBroadcast(Intent arg0, String arg1, Bundle arg2) {
// pass
}
@Override
public void sendBroadcast(Intent intent, String receiverPermission, int appOp) {
// pass
}
@Override
public void sendOrderedBroadcast(Intent arg0, String arg1) {
// pass
}
@Override
public void sendOrderedBroadcast(Intent arg0, String arg1,
BroadcastReceiver arg2, Handler arg3, int arg4, String arg5,
Bundle arg6) {
// pass
}
@Override
public void sendOrderedBroadcast(Intent arg0, String arg1,
Bundle arg7, BroadcastReceiver arg2, Handler arg3, int arg4, String arg5,
Bundle arg6) {
// pass
}
@Override
public void sendOrderedBroadcast(Intent intent, String receiverPermission, int appOp,
BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
String initialData, Bundle initialExtras) {
// pass
}
@Override
public void sendBroadcastAsUser(Intent intent, UserHandle user) {
// pass
}
@Override
public void sendBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission) {
// pass
}
@Override
public void sendBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission, Bundle options) {
// pass
}
public void sendBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission, int appOp) {
// pass
}
@Override
public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
int initialCode, String initialData, Bundle initialExtras) {
// pass
}
@Override
public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
Handler scheduler,
int initialCode, String initialData, Bundle initialExtras) {
// pass
}
@Override
public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission, int appOp, Bundle options, BroadcastReceiver resultReceiver,
Handler scheduler,
int initialCode, String initialData, Bundle initialExtras) {
// pass
}
@Override
public void sendStickyBroadcast(Intent arg0) {
// pass
}
@Override
public void sendStickyOrderedBroadcast(Intent intent,
BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData,
Bundle initialExtras) {
// pass
}
@Override
public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
// pass
}
@Override
public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) {
// pass
}
@Override
public void sendStickyOrderedBroadcastAsUser(Intent intent,
UserHandle user, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,
Bundle initialExtras) {
// pass
}
@Override
public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) {
// pass
}
@Override
public void setTheme(int arg0) {
// pass
}
@Override
public void setWallpaper(Bitmap arg0) throws IOException {
// pass
}
@Override
public void setWallpaper(InputStream arg0) throws IOException {
// pass
}
@Override
public void startActivity(Intent arg0) {
// pass
}
@Override
public void startActivity(Intent arg0, Bundle arg1) {
// pass
}
@Override
public void startIntentSender(IntentSender intent,
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
throws IntentSender.SendIntentException {
// pass
}
@Override
public void startIntentSender(IntentSender intent,
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
Bundle options) throws IntentSender.SendIntentException {
// pass
}
@Override
public boolean startInstrumentation(ComponentName arg0, String arg1,
Bundle arg2) {
// pass
return false;
}
@Override
public ComponentName startService(Intent arg0) {
// pass
return null;
}
@Override
public ComponentName startForegroundService(Intent service) {
// pass
return null;
}
@Override
public ComponentName startForegroundServiceAsUser(Intent service, UserHandle user) {
// pass
return null;
}
@Override
public boolean stopService(Intent arg0) {
// pass
return false;
}
@Override
public ComponentName startServiceAsUser(Intent arg0, UserHandle arg1) {
// pass
return null;
}
@Override
public boolean stopServiceAsUser(Intent arg0, UserHandle arg1) {
// pass
return false;
}
@Override
public void updateServiceGroup(@NonNull ServiceConnection conn, int group,
int importance) {
// pass
}
@Override
public void unbindService(ServiceConnection arg0) {
// pass
}
@Override
public void unregisterReceiver(BroadcastReceiver arg0) {
// pass
}
@Override
public Context getApplicationContext() {
return this;
}
@Override
public void startActivities(Intent[] arg0) {
// pass
}
@Override
public void startActivities(Intent[] arg0, Bundle arg1) {
// pass
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public File getObbDir() {
Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "OBB not supported", null, null);
return null;
}
@Override
public DisplayAdjustments getDisplayAdjustments(int displayId) {
// pass
return null;
}
@Override
public Display getDisplay() {
// pass
return null;
}
@Override
public int getDisplayId() {
// pass
return 0;
}
@Override
public void updateDisplay(int displayId) {
// pass
}
@Override
public int getUserId() {
return 0; // not used
}
@Override
public File[] getExternalFilesDirs(String type) {
// pass
return new File[0];
}
@Override
public File[] getObbDirs() {
// pass
return new File[0];
}
@Override
public File[] getExternalCacheDirs() {
// pass
return new File[0];
}
@Override
public File[] getExternalMediaDirs() {
// pass
return new File[0];
}
public void setScrollYPos(@NonNull View view, int scrollPos) {
mScrollYPos.put(view, scrollPos);
}
public int getScrollYPos(@NonNull View view) {
Integer pos = mScrollYPos.get(view);
return pos != null ? pos : 0;
}
public void setScrollXPos(@NonNull View view, int scrollPos) {
mScrollXPos.put(view, scrollPos);
}
public int getScrollXPos(@NonNull View view) {
Integer pos = mScrollXPos.get(view);
return pos != null ? pos : 0;
}
@Override
public Context createDeviceProtectedStorageContext() {
// pass
return null;
}
@Override
public Context createCredentialProtectedStorageContext() {
// pass
return null;
}
@Override
public boolean isDeviceProtectedStorage() {
return false;
}
@Override
public boolean isCredentialProtectedStorage() {
return false;
}
@Override
public boolean canLoadUnsafeResources() {
return true;
}
@Override
public boolean isUiContext() {
return true;
}
/**
* Returns whether shadows should be rendered or not
*/
public boolean isShadowsEnabled() {
return mShadowsEnabled;
}
/**
* Returns whether high quality shadows should be used
*/
public boolean isHighQualityShadows() {
return mHighQualityShadows;
}
public <T> void putUserData(@NonNull Key<T> key, @Nullable T data) {
mUserData.put(key, data);
}
@SuppressWarnings("unchecked")
@Nullable
public <T> T getUserData(@NonNull Key<T> key) {
return (T) mUserData.get(key);
}
/** Logs an error message to the error log of the host application. */
public void error(@NonNull String message, @NonNull String... details) {
mLayoutlibCallback.error(message, details);
}
/** Logs an error message to the error log of the host application. */
public void error(@NonNull String message, @Nullable Throwable t) {
mLayoutlibCallback.error(message, t);
}
/** Logs an error message to the error log of the host application. */
public void error(@NonNull Throwable t) {
mLayoutlibCallback.error(t);
}
/** Logs a warning to the log of the host application. */
public void warn(@NonNull String message, @Nullable Throwable t) {
mLayoutlibCallback.warn(message, t);
}
/** Logs a warning to the log of the host application. */
public void warn(@NonNull Throwable t) {
mLayoutlibCallback.warn(t);
}
/**
* No two Key instances are considered equal.
*
* @param <T> the type of values associated with the key
*/
public static final class Key<T> {
private final String name;
@NonNull
public static <T> Key<T> create(@NonNull String name) {
return new Key<T>(name);
}
private Key(@NonNull String name) {
this.name = name;
}
/** For debugging only. */
@Override
public String toString() {
return name;
}
}
private class AttributeHolder {
private final int resourceId;
@NonNull private final ResourceReference reference;
private AttributeHolder(int resourceId, @NonNull ResourceReference reference) {
this.resourceId = resourceId;
this.reference = reference;
}
@NonNull
private ResourceReference asReference() {
return reference;
}
private int getResourceId() {
return resourceId;
}
@NonNull
private String getName() {
return reference.getName();
}
@NonNull
private ResourceNamespace getNamespace() {
return reference.getNamespace();
}
}
/**
* The cached value depends on
* <ol>
* <li>{@code int[]}: the attributes for which TypedArray is created </li>
* <li>{@code List<StyleResourceValue>}: the themes set on the context at the time of
* creation of the TypedArray</li>
* <li>{@code Integer}: the default style used at the time of creation</li>
* </ol>
*
* The class is created by using nested maps resolving one dependency at a time.
* <p/>
* The final value of the nested maps is a pair of the typed array and a map of properties
* that should be added to {@link #mDefaultPropMaps}, if needed.
*/
private static class TypedArrayCache {
private Map<int[],
Map<List<StyleResourceValue>,
Map<Integer, Pair<BridgeTypedArray,
Map<ResourceReference, ResourceValue>>>>> mCache;
private TypedArrayCache() {
mCache = new IdentityHashMap<>();
}
public Pair<BridgeTypedArray, Map<ResourceReference, ResourceValue>> get(int[] attrs,
List<StyleResourceValue> themes, int resId) {
Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, Map<ResourceReference,
ResourceValue>>>>
cacheFromThemes = mCache.get(attrs);
if (cacheFromThemes != null) {
Map<Integer, Pair<BridgeTypedArray, Map<ResourceReference, ResourceValue>>> cacheFromResId =
cacheFromThemes.get(themes);
if (cacheFromResId != null) {
return cacheFromResId.get(resId);
}
}
return null;
}
public void put(int[] attrs, List<StyleResourceValue> themes, int resId,
Pair<BridgeTypedArray, Map<ResourceReference, ResourceValue>> value) {
Map<List<StyleResourceValue>, Map<Integer, Pair<BridgeTypedArray, Map<ResourceReference,
ResourceValue>>>>
cacheFromThemes = mCache.computeIfAbsent(attrs, k -> new HashMap<>());
Map<Integer, Pair<BridgeTypedArray, Map<ResourceReference, ResourceValue>>> cacheFromResId =
cacheFromThemes.computeIfAbsent(themes, k -> new HashMap<>());
cacheFromResId.put(resId, value);
}
}
}