diff options
18 files changed, 422 insertions, 62 deletions
diff --git a/api/current.txt b/api/current.txt index 35041d1d74d2..3919fb8eb2d2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -45254,7 +45254,7 @@ package android.webkit { method public static boolean allowFileSchemeCookies(); method public abstract void flush(); method public abstract java.lang.String getCookie(java.lang.String); - method public static synchronized android.webkit.CookieManager getInstance(); + method public static android.webkit.CookieManager getInstance(); method public abstract boolean hasCookies(); method public abstract deprecated void removeAllCookie(); method public abstract void removeAllCookies(android.webkit.ValueCallback<java.lang.Boolean>); @@ -45269,8 +45269,8 @@ package android.webkit { } public final deprecated class CookieSyncManager extends android.webkit.WebSyncManager { - method public static synchronized android.webkit.CookieSyncManager createInstance(android.content.Context); - method public static synchronized android.webkit.CookieSyncManager getInstance(); + method public static android.webkit.CookieSyncManager createInstance(android.content.Context); + method public static android.webkit.CookieSyncManager getInstance(); method protected deprecated void syncFromRamToFlash(); field protected static final java.lang.String LOGTAG = "websync"; field protected android.webkit.WebViewDatabase mDataBase; diff --git a/api/system-current.txt b/api/system-current.txt index 5c6c122a68a0..44ee2c9fdfb6 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -48416,7 +48416,7 @@ package android.webkit { method public abstract java.lang.String getCookie(java.lang.String); method public abstract java.lang.String getCookie(java.lang.String, boolean); method public synchronized java.lang.String getCookie(android.net.WebAddress); - method public static synchronized android.webkit.CookieManager getInstance(); + method public static android.webkit.CookieManager getInstance(); method public abstract boolean hasCookies(); method public abstract boolean hasCookies(boolean); method public abstract deprecated void removeAllCookie(); @@ -48433,8 +48433,8 @@ package android.webkit { } public final deprecated class CookieSyncManager extends android.webkit.WebSyncManager { - method public static synchronized android.webkit.CookieSyncManager createInstance(android.content.Context); - method public static synchronized android.webkit.CookieSyncManager getInstance(); + method public static android.webkit.CookieSyncManager createInstance(android.content.Context); + method public static android.webkit.CookieSyncManager getInstance(); method protected deprecated void syncFromRamToFlash(); field protected static final java.lang.String LOGTAG = "websync"; field protected android.webkit.WebViewDatabase mDataBase; diff --git a/api/test-current.txt b/api/test-current.txt index 4d55c29a6e00..9e3387f025b4 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -45334,7 +45334,7 @@ package android.webkit { method public static boolean allowFileSchemeCookies(); method public abstract void flush(); method public abstract java.lang.String getCookie(java.lang.String); - method public static synchronized android.webkit.CookieManager getInstance(); + method public static android.webkit.CookieManager getInstance(); method public abstract boolean hasCookies(); method public abstract deprecated void removeAllCookie(); method public abstract void removeAllCookies(android.webkit.ValueCallback<java.lang.Boolean>); @@ -45349,8 +45349,8 @@ package android.webkit { } public final deprecated class CookieSyncManager extends android.webkit.WebSyncManager { - method public static synchronized android.webkit.CookieSyncManager createInstance(android.content.Context); - method public static synchronized android.webkit.CookieSyncManager getInstance(); + method public static android.webkit.CookieSyncManager createInstance(android.content.Context); + method public static android.webkit.CookieSyncManager getInstance(); method protected deprecated void syncFromRamToFlash(); field protected static final java.lang.String LOGTAG = "websync"; field protected android.webkit.WebViewDatabase mDataBase; diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index 3ec235b6e3cd..67289c28e7a1 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -35,7 +35,7 @@ public abstract class CookieManager { * * @return the singleton CookieManager instance */ - public static synchronized CookieManager getInstance() { + public static CookieManager getInstance() { return WebViewFactory.getProvider().getCookieManager(); } diff --git a/core/java/android/webkit/CookieSyncManager.java b/core/java/android/webkit/CookieSyncManager.java index eda8d3605112..c974b328d70f 100644 --- a/core/java/android/webkit/CookieSyncManager.java +++ b/core/java/android/webkit/CookieSyncManager.java @@ -65,6 +65,7 @@ public final class CookieSyncManager extends WebSyncManager { private static CookieSyncManager sRef; private static boolean sGetInstanceAllowed = false; + private static final Object sLock = new Object(); private CookieSyncManager() { super(null, null); @@ -77,12 +78,14 @@ public final class CookieSyncManager extends WebSyncManager { * * @return CookieSyncManager */ - public static synchronized CookieSyncManager getInstance() { - checkInstanceIsAllowed(); - if (sRef == null) { - sRef = new CookieSyncManager(); + public static CookieSyncManager getInstance() { + synchronized (sLock) { + checkInstanceIsAllowed(); + if (sRef == null) { + sRef = new CookieSyncManager(); + } + return sRef; } - return sRef; } /** @@ -90,12 +93,14 @@ public final class CookieSyncManager extends WebSyncManager { * @param context * @return CookieSyncManager */ - public static synchronized CookieSyncManager createInstance(Context context) { - if (context == null) { - throw new IllegalArgumentException("Invalid context argument"); + public static CookieSyncManager createInstance(Context context) { + synchronized (sLock) { + if (context == null) { + throw new IllegalArgumentException("Invalid context argument"); + } + setGetInstanceIsAllowed(); + return getInstance(); } - setGetInstanceIsAllowed(); - return getInstance(); } /** diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index cbfe43a230f6..6f3fa3685b8d 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2322,7 +2322,7 @@ public class WebView extends AbsoluteLayout } } - private static synchronized WebViewFactoryProvider getFactory() { + private static WebViewFactoryProvider getFactory() { return WebViewFactory.getProvider(); } diff --git a/packages/EasterEgg/src/com/android/egg/neko/NekoLand.java b/packages/EasterEgg/src/com/android/egg/neko/NekoLand.java index 86f813e0647a..3629162cdd47 100644 --- a/packages/EasterEgg/src/com/android/egg/neko/NekoLand.java +++ b/packages/EasterEgg/src/com/android/egg/neko/NekoLand.java @@ -24,6 +24,7 @@ import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.media.MediaScannerConnection; import android.net.Uri; @@ -50,6 +51,9 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; public class NekoLand extends Activity implements PrefsListener { public static boolean DEBUG = false; @@ -98,7 +102,19 @@ public class NekoLand extends Activity implements PrefsListener { cats[i] = Cat.create(this); } } else { - cats = mPrefs.getCats().toArray(new Cat[0]); + final float[] hsv = new float[3]; + List<Cat> list = mPrefs.getCats(); + Collections.sort(list, new Comparator<Cat>() { + @Override + public int compare(Cat cat, Cat cat2) { + Color.colorToHSV(cat.getBodyColor(), hsv); + float bodyH1 = hsv[0]; + Color.colorToHSV(cat2.getBodyColor(), hsv); + float bodyH2 = hsv[0]; + return Float.compare(bodyH1, bodyH2); + } + }); + cats = list.toArray(new Cat[0]); } mAdapter.setCats(cats); return cats.length; diff --git a/packages/SettingsLib/res/values-lv/arrays.xml b/packages/SettingsLib/res/values-lv/arrays.xml index fe134dbd4080..801de5d8fa4d 100644 --- a/packages/SettingsLib/res/values-lv/arrays.xml +++ b/packages/SettingsLib/res/values-lv/arrays.xml @@ -80,8 +80,18 @@ <item msgid="3606047780792894151">"4 MB vienam žurnāla buferim"</item> <item msgid="5431354956856655120">"16 MB vienam žurnāla buferim"</item> </string-array> - <!-- no translation found for select_logpersist_titles:3 (2850427388488887328) --> - <!-- no translation found for select_logpersist_summaries:3 (8489661142527693381) --> + <string-array name="select_logpersist_titles"> + <item msgid="1744840221860799971">"Izslēgts"</item> + <item msgid="3054662377365844197">"Visi"</item> + <item msgid="688870735111627832">"Visi, izņemot radio"</item> + <item msgid="2850427388488887328">"tikai kodolā"</item> + </string-array> + <string-array name="select_logpersist_summaries"> + <item msgid="2216470072500521830">"Izslēgts"</item> + <item msgid="172978079776521897">"Visi reģistra buferi"</item> + <item msgid="3873873912383879240">"Visi, izņemot radio reģistra buferus"</item> + <item msgid="8489661142527693381">"tikai kodola žurnāla buferī"</item> + </string-array> <string-array name="window_animation_scale_entries"> <item msgid="8134156599370824081">"Animācija ir izslēgta"</item> <item msgid="6624864048416710414">"Animācijas mērogs: 0,5x"</item> diff --git a/packages/SettingsLib/res/values-zh-rCN/arrays.xml b/packages/SettingsLib/res/values-zh-rCN/arrays.xml index 659007ba5fb4..8a3febdaadd5 100644 --- a/packages/SettingsLib/res/values-zh-rCN/arrays.xml +++ b/packages/SettingsLib/res/values-zh-rCN/arrays.xml @@ -80,8 +80,18 @@ <item msgid="3606047780792894151">"每个日志缓冲区 4M"</item> <item msgid="5431354956856655120">"每个日志缓冲区 16M"</item> </string-array> - <!-- no translation found for select_logpersist_titles:3 (2850427388488887328) --> - <!-- no translation found for select_logpersist_summaries:3 (8489661142527693381) --> + <string-array name="select_logpersist_titles"> + <item msgid="1744840221860799971">"关闭"</item> + <item msgid="3054662377365844197">"全部"</item> + <item msgid="688870735111627832">"所有非无线电"</item> + <item msgid="2850427388488887328">"仅限内核"</item> + </string-array> + <string-array name="select_logpersist_summaries"> + <item msgid="2216470072500521830">"关闭"</item> + <item msgid="172978079776521897">"所有日志缓冲区"</item> + <item msgid="3873873912383879240">"所有非无线电日志缓冲区"</item> + <item msgid="8489661142527693381">"仅限内核日志缓冲区"</item> + </string-array> <string-array name="window_animation_scale_entries"> <item msgid="8134156599370824081">"关闭动画"</item> <item msgid="6624864048416710414">"动画缩放 0.5x"</item> diff --git a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java index 53c2fd7ce543..407d315f9973 100644 --- a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java +++ b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java @@ -30,7 +30,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; -import libcore.io.IoUtils; +import libcore.io.IoBridge; import libcore.util.HexEncoding; import java.io.FileDescriptor; @@ -457,7 +457,9 @@ public class RouterAdvertisementDaemon { private void closeSocket() { if (mSocket != null) { - IoUtils.closeQuietly(mSocket); + try { + IoBridge.closeAndSignalBlockedThreads(mSocket); + } catch (IOException ignored) {} } mSocket = null; } diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java index 1dfd305a6094..2903e3a4b445 100644 --- a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java @@ -91,16 +91,14 @@ public class VectorDrawable_Delegate { @LayoutlibDelegate static long nCreateTree(long rootGroupPtr) { - VGroup_Delegate rootGroup = VNativeObject.getDelegate(rootGroupPtr); - return sPathManager.addNewDelegate(new VPathRenderer_Delegate(rootGroup)); + return sPathManager.addNewDelegate(new VPathRenderer_Delegate(rootGroupPtr)); } @LayoutlibDelegate static long nCreateTreeFromCopy(long rendererToCopyPtr, long rootGroupPtr) { - VGroup_Delegate rootGroup = VNativeObject.getDelegate(rootGroupPtr); VPathRenderer_Delegate rendererToCopy = VNativeObject.getDelegate(rendererToCopyPtr); return sPathManager.addNewDelegate(new VPathRenderer_Delegate(rendererToCopy, - rootGroup)); + rootGroupPtr)); } @LayoutlibDelegate @@ -163,7 +161,6 @@ public class VectorDrawable_Delegate { @LayoutlibDelegate static long nCreateFullPath(long nativeFullPathPtr) { VFullPath_Delegate original = VNativeObject.getDelegate(nativeFullPathPtr); - return sPathManager.addNewDelegate(new VFullPath_Delegate(original)); } @@ -247,8 +244,7 @@ public class VectorDrawable_Delegate { @LayoutlibDelegate static long nCreateGroup(long groupPtr) { VGroup_Delegate original = VNativeObject.getDelegate(groupPtr); - return sPathManager.addNewDelegate( - new VGroup_Delegate(original, new ArrayMap<String, Object>())); + return sPathManager.addNewDelegate(new VGroup_Delegate(original, new ArrayMap<>())); } @LayoutlibDelegate @@ -1029,7 +1025,7 @@ public class VectorDrawable_Delegate { private final Path mPath; private final Path mRenderPath; private final Matrix mFinalPathMatrix = new Matrix(); - private final VGroup_Delegate mRootGroup; + private final long mRootGroupPtr; private float mViewportWidth = 0; private float mViewportHeight = 0; private float mRootAlpha = 1.0f; @@ -1037,15 +1033,15 @@ public class VectorDrawable_Delegate { private Paint mFillPaint; private PathMeasure mPathMeasure; - private VPathRenderer_Delegate(VGroup_Delegate rootGroup) { - mRootGroup = rootGroup; + private VPathRenderer_Delegate(long rootGroupPtr) { + mRootGroupPtr = rootGroupPtr; mPath = new Path(); mRenderPath = new Path(); } private VPathRenderer_Delegate(VPathRenderer_Delegate rendererToCopy, - VGroup_Delegate rootGroup) { - this(rootGroup); + long rootGroupPtr) { + this(rootGroupPtr); mViewportWidth = rendererToCopy.mViewportWidth; mViewportHeight = rendererToCopy.mViewportHeight; mRootAlpha = rendererToCopy.mRootAlpha; @@ -1087,7 +1083,7 @@ public class VectorDrawable_Delegate { public void draw(long canvasPtr, long filterPtr, int w, int h) { // Traverse the tree in pre-order to draw. - drawGroupTree(mRootGroup, Matrix.IDENTITY_MATRIX, canvasPtr, w, h, filterPtr); + drawGroupTree(VNativeObject.getDelegate(mRootGroupPtr), Matrix.IDENTITY_MATRIX, canvasPtr, w, h, filterPtr); } private void drawPath(VGroup_Delegate VGroup, VPath_Delegate VPath, long canvasPtr, @@ -1227,5 +1223,14 @@ public class VectorDrawable_Delegate { @Override public void setName(String name) { } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + + // The mRootGroupPtr is not explicitly freed by anything in the VectorDrawable so we + // need to free it here. + sPathManager.removeJavaReferenceFor(mRootGroupPtr); + } } } diff --git a/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java b/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java index 4f00b5da08a8..aa393a976d12 100644 --- a/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java +++ b/tools/layoutlib/bridge/src/android/preference/BridgePreferenceInflater.java @@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import android.content.Context; import android.util.AttributeSet; +import android.view.InflateException; public class BridgePreferenceInflater extends PreferenceInflater { @@ -42,7 +43,15 @@ public class BridgePreferenceInflater extends PreferenceInflater { viewKey = ((BridgeXmlBlockParser) attrs).getViewCookie(); } - Preference preference = super.onCreateItem(name, attrs); + Preference preference = null; + try { + preference = super.onCreateItem(name, attrs); + } catch (ClassNotFoundException | InflateException exception) { + // name is probably not a valid preference type + if ("SwitchPreferenceCompat".equals(name)) { + preference = super.onCreateItem("SwitchPreference", attrs); + } + } if (viewKey != null && bc != null) { bc.addCookie(preference, viewKey); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java index d432120ccb6f..ab278195f328 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/RecyclerViewUtil.java @@ -21,6 +21,7 @@ import com.android.ide.common.rendering.api.LayoutlibCallback; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.RenderParamsFlags; +import com.android.layoutlib.bridge.util.ReflectionUtils; import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException; import android.annotation.NonNull; @@ -116,7 +117,7 @@ public class RecyclerViewUtil { private static void setProperty(@NonNull Object object, @NonNull String propertyClassName, @NonNull Object propertyValue, @NonNull String propertySetter) throws ReflectionException { - Class<?> propertyClass = getClassInstance(propertyValue, propertyClassName); + Class<?> propertyClass = ReflectionUtils.getClassInstance(propertyValue, propertyClassName); setProperty(object, propertyClass, propertyValue, propertySetter); } @@ -126,22 +127,4 @@ public class RecyclerViewUtil { invoke(getMethod(object.getClass(), propertySetter, propertyClass), object, propertyValue); } - /** - * Looks through the class hierarchy of {@code object} at runtime and returns the class matching - * the name {@code className}. - * <p/> - * This is used when we cannot use Class.forName() since the class we want was loaded from a - * different ClassLoader. - */ - @NonNull - private static Class<?> getClassInstance(@NonNull Object object, @NonNull String className) { - Class<?> superClass = object.getClass(); - while (superClass != null) { - if (className.equals(superClass.getName())) { - return superClass; - } - superClass = superClass.getSuperclass(); - } - throw new RuntimeException("invalid object/classname combination."); - } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java new file mode 100644 index 000000000000..0124e83d79e8 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/support/SupportPreferencesUtil.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2016 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.support; + +import com.android.ide.common.rendering.api.LayoutlibCallback; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.StyleResourceValue; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; +import com.android.layoutlib.bridge.util.ReflectionUtils.ReflectionException; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.view.ContextThemeWrapper; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.LinearLayout.LayoutParams; +import android.widget.ScrollView; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; + +import static com.android.layoutlib.bridge.util.ReflectionUtils.getClassInstance; +import static com.android.layoutlib.bridge.util.ReflectionUtils.getMethod; +import static com.android.layoutlib.bridge.util.ReflectionUtils.invoke; + +/** + * Class with utility methods to instantiate Preferences provided by the support library. + * This class uses reflection to access the support preference objects so it heavily depends on + * the API being stable. + */ +public class SupportPreferencesUtil { + private static final String PREFERENCE_PKG = "android.support.v7.preference"; + private static final String PREFERENCE_MANAGER = PREFERENCE_PKG + ".PreferenceManager"; + private static final String PREFERENCE_GROUP = PREFERENCE_PKG + ".PreferenceGroup"; + private static final String PREFERENCE_GROUP_ADAPTER = + PREFERENCE_PKG + ".PreferenceGroupAdapter"; + private static final String PREFERENCE_INFLATER = PREFERENCE_PKG + ".PreferenceInflater"; + + private SupportPreferencesUtil() { + } + + @NonNull + private static Object instantiateClass(@NonNull LayoutlibCallback callback, + @NonNull String className, @Nullable Class[] constructorSignature, + @Nullable Object[] constructorArgs) throws ReflectionException { + try { + Object instance = callback.loadClass(className, constructorSignature, constructorArgs); + if (instance == null) { + throw new ClassNotFoundException(className + " class not found"); + } + return instance; + } catch (ClassNotFoundException e) { + throw new ReflectionException(e); + } + } + + @NonNull + private static Object createPreferenceGroupAdapter(@NonNull LayoutlibCallback callback, + @NonNull Object preferenceScreen) throws ReflectionException { + Class<?> preferenceGroupClass = getClassInstance(preferenceScreen, PREFERENCE_GROUP); + + return instantiateClass(callback, PREFERENCE_GROUP_ADAPTER, + new Class[]{preferenceGroupClass}, new Object[]{preferenceScreen}); + } + + @NonNull + private static Object createInflatedPreference(@NonNull LayoutlibCallback callback, + @NonNull Context context, @NonNull XmlPullParser parser, @NonNull Object preferenceScreen, + @NonNull Object preferenceManager) throws ReflectionException { + Class<?> preferenceGroupClass = getClassInstance(preferenceScreen, PREFERENCE_GROUP); + Object preferenceInflater = instantiateClass(callback, PREFERENCE_INFLATER, + new Class[]{Context.class, preferenceManager.getClass()}, + new Object[]{context, preferenceManager}); + Object inflatedPreference = invoke( + getMethod(preferenceInflater.getClass(), "inflate", XmlPullParser.class, + preferenceGroupClass), preferenceInflater, parser, null); + + if (inflatedPreference == null) { + throw new ReflectionException("inflate method returned null"); + } + + return inflatedPreference; + } + + /** + * Returns a themed wrapper context of {@link BridgeContext} with the theme specified in + * ?attr/preferenceTheme applied to it. + */ + @Nullable + private static Context getThemedContext(@NonNull BridgeContext bridgeContext) { + RenderResources resources = bridgeContext.getRenderResources(); + ResourceValue preferenceTheme = resources.findItemInTheme("preferenceTheme", false); + + if (preferenceTheme != null) { + // resolve it, if needed. + preferenceTheme = resources.resolveResValue(preferenceTheme); + } + if (preferenceTheme instanceof StyleResourceValue) { + int styleId = bridgeContext.getDynamicIdByStyle(((StyleResourceValue) preferenceTheme)); + if (styleId != 0) { + return new ContextThemeWrapper(bridgeContext, styleId); + } + } + + return null; + } + + /** + * Returns a {@link LinearLayout} containing all the UI widgets representing the preferences + * passed in the group adapter. + */ + @Nullable + private static LinearLayout setUpPreferencesListView(@NonNull BridgeContext bridgeContext, + @NonNull Context themedContext, @NonNull ArrayList<Object> viewCookie, + @NonNull Object preferenceGroupAdapter) throws ReflectionException { + // Setup the LinearLayout that will contain the preferences + LinearLayout listView = new LinearLayout(themedContext); + listView.setOrientation(LinearLayout.VERTICAL); + listView.setLayoutParams( + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + + if (!viewCookie.isEmpty()) { + bridgeContext.addViewKey(listView, viewCookie.get(0)); + } + + // Get all the preferences and add them to the LinearLayout + Integer preferencesCount = + (Integer) invoke(getMethod(preferenceGroupAdapter.getClass(), "getItemCount"), + preferenceGroupAdapter); + if (preferencesCount == null) { + return listView; + } + + Method getItemId = getMethod(preferenceGroupAdapter.getClass(), "getItemId", int.class); + Method getItemViewType = + getMethod(preferenceGroupAdapter.getClass(), "getItemViewType", int.class); + Method onCreateViewHolder = + getMethod(preferenceGroupAdapter.getClass(), "onCreateViewHolder", ViewGroup.class, + int.class); + for (int i = 0; i < preferencesCount; i++) { + Long id = (Long) invoke(getItemId, preferenceGroupAdapter, i); + if (id == null) { + continue; + } + + // Get the type of the preference layout and bind it to a newly created view holder + Integer type = (Integer) invoke(getItemViewType, preferenceGroupAdapter, i); + Object viewHolder = + invoke(onCreateViewHolder, preferenceGroupAdapter, listView, type); + if (viewHolder == null) { + continue; + } + invoke(getMethod(preferenceGroupAdapter.getClass(), "onBindViewHolder", + viewHolder.getClass(), int.class), preferenceGroupAdapter, viewHolder, i); + + try { + // Get the view from the view holder and add it to our layout + View itemView = + (View) viewHolder.getClass().getField("itemView").get(viewHolder); + + int arrayPosition = id.intValue() - 1; // IDs are 1 based + if (arrayPosition >= 0 && arrayPosition < viewCookie.size()) { + bridgeContext.addViewKey(itemView, viewCookie.get(arrayPosition)); + } + listView.addView(itemView); + } catch (IllegalAccessException | NoSuchFieldException ignored) { + } + } + + return listView; + } + + /** + * Inflates a preferences layout using the support library. If the support library is not + * available, this method will return null without advancing the parsers. + */ + @Nullable + public static View inflatePreference(@NonNull BridgeContext bridgeContext, + @NonNull XmlPullParser parser, @Nullable ViewGroup root) { + try { + LayoutlibCallback callback = bridgeContext.getLayoutlibCallback(); + + Context context = getThemedContext(bridgeContext); + if (context == null) { + // Probably we couldn't find the "preferenceTheme" in the theme + return null; + } + + // Create PreferenceManager + Object preferenceManager = + instantiateClass(callback, PREFERENCE_MANAGER, new Class[]{Context.class}, + new Object[]{context}); + + // From this moment on, we can assume that we found the support library and that + // nothing should fail + + // Create PreferenceScreen + Object preferenceScreen = + invoke(getMethod(preferenceManager.getClass(), "createPreferenceScreen", + Context.class), preferenceManager, context); + if (preferenceScreen == null) { + return null; + } + + // Setup a parser that stores the list of cookies in the same order as the preferences + // are inflated. That way we can later reconstruct the list using the preference id + // since they are sequential and start in 1. + ArrayList<Object> viewCookie = new ArrayList<>(); + if (parser instanceof BridgeXmlBlockParser) { + // Setup a parser that stores the XmlTag + parser = new BridgeXmlBlockParser(parser, null, false) { + @Override + public Object getViewCookie() { + return ((BridgeXmlBlockParser) getParser()).getViewCookie(); + } + + @Override + public int next() throws XmlPullParserException, IOException { + int ev = super.next(); + if (ev == XmlPullParser.START_TAG) { + viewCookie.add(this.getViewCookie()); + } + + return ev; + } + }; + } + + // Create the PreferenceInflater + Object inflatedPreference = + createInflatedPreference(callback, context, parser, preferenceScreen, + preferenceManager); + + // Setup the RecyclerView (set adapter and layout manager) + Object preferenceGroupAdapter = + createPreferenceGroupAdapter(callback, inflatedPreference); + + // Instead of just setting the group adapter as adapter for a RecyclerView, we manually + // get all the items and add them to a LinearLayout. This allows us to set the view + // cookies so the preferences are correctly linked to their XML. + LinearLayout listView = setUpPreferencesListView(bridgeContext, context, viewCookie, + preferenceGroupAdapter); + + ScrollView scrollView = new ScrollView(context); + scrollView.setLayoutParams( + new ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + scrollView.addView(listView); + + if (root != null) { + root.addView(scrollView); + } + + return scrollView; + } catch (ReflectionException e) { + return null; + } + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java index 0c39026a15d8..ea40ba78e58a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/DelegateManager.java @@ -165,5 +165,6 @@ public final class DelegateManager<T> { int idx = sDelegates.indexOfValue(reference); out.printf("[%d] %s\n", sDelegates.keyAt(idx), reference.getClass().getSimpleName()); } + out.printf("\nTotal number of objects: %d\n", sJavaReferences.size()); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index c890793e290f..feed04509075 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -45,6 +45,7 @@ import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.layoutlib.bridge.android.RenderParamsFlags; import com.android.layoutlib.bridge.android.graphics.NopCanvas; import com.android.layoutlib.bridge.android.support.DesignLibUtil; +import com.android.layoutlib.bridge.android.support.SupportPreferencesUtil; import com.android.layoutlib.bridge.impl.binding.FakeAdapter; import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter; import com.android.resources.ResourceType; @@ -326,8 +327,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { boolean isPreference = "PreferenceScreen".equals(rootTag); View view; if (isPreference) { - view = Preference_Delegate.inflatePreference(getContext(), mBlockParser, + // First try to use the support library inflater. If something fails, fallback + // to the system preference inflater. + view = SupportPreferencesUtil.inflatePreference(getContext(), mBlockParser, mContentRoot); + if (view == null) { + view = Preference_Delegate.inflatePreference(getContext(), mBlockParser, + mContentRoot); + } } else { view = mInflater.inflate(mBlockParser, mContentRoot); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java index 7ce27b6a55fa..040191e859ba 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/ReflectionUtils.java @@ -37,6 +37,15 @@ public class ReflectionUtils { } } + @NonNull + public static Method getAccessibleMethod(@NonNull Class<?> clazz, @NonNull String name, + @Nullable Class<?>... params) throws ReflectionException { + Method method = getMethod(clazz, name, params); + method.setAccessible(true); + + return method; + } + @Nullable public static Object invoke(@NonNull Method method, @Nullable Object object, @Nullable Object... args) throws ReflectionException { @@ -74,6 +83,25 @@ public class ReflectionUtils { } /** + * Looks through the class hierarchy of {@code object} at runtime and returns the class matching + * the name {@code className}. + * <p> + * This is used when we cannot use Class.forName() since the class we want was loaded from a + * different ClassLoader. + */ + @NonNull + public static Class<?> getClassInstance(@NonNull Object object, @NonNull String className) { + Class<?> superClass = object.getClass(); + while (superClass != null) { + if (className.equals(superClass.getName())) { + return superClass; + } + superClass = superClass.getSuperclass(); + } + throw new RuntimeException("invalid object/classname combination."); + } + + /** * Wraps all reflection related exceptions. Created since ReflectiveOperationException was * introduced in 1.7 and we are still on 1.6 */ diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java index ba687fe384e6..d16026845db1 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java @@ -349,7 +349,11 @@ public class Main { obj = null; while(ref.get() != null) { System.gc(); + System.runFinalization(); } + + System.gc(); + System.runFinalization(); } @AfterClass |