From b556decf75b2b084e1aed54ac7fa23a141eedb7f Mon Sep 17 00:00:00 2001 From: Deepanshu Gupta Date: Thu, 6 Aug 2015 15:32:44 -0700 Subject: [PATCH] LayoutLib: Switch SimpleDateFormat to icu. Use ICU's implementation of SimpleDateFormat in LayoutLib. The format patterns used are not supported by java.text.SimpleDateFormat. The change required adding a mechanism for promoting visibility of fields in the framework in the create tool. TODO: Add additional tests in the create tool for this new functionality. Change-Id: Id0f4be41f9731c42a28919c32cc5ef271a656982 --- .../.idea/inspectionProfiles/Project_Default.xml | 2 + .../android/widget/SimpleMonthView_Delegate.java | 99 ++++++++++++++++++++++ .../layoutlib/bridge/impl/RenderAction.java | 2 + .../tools/layoutlib/create/AsmGenerator.java | 46 +++++++--- .../android/tools/layoutlib/create/CreateInfo.java | 13 +++ .../tools/layoutlib/create/ICreateInfo.java | 7 ++ .../layoutlib/create/PromoteFieldClassAdapter.java | 52 ++++++++++++ .../tools/layoutlib/create/AsmGeneratorTest.java | 20 +++++ 8 files changed, 227 insertions(+), 14 deletions(-) create mode 100644 tools/layoutlib/bridge/src/android/widget/SimpleMonthView_Delegate.java create mode 100644 tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java diff --git a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml index 0ac7a44ea38a..5bb3e3e47922 100644 --- a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml +++ b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml @@ -1,3 +1,4 @@ + \ No newline at end of file diff --git a/tools/layoutlib/bridge/src/android/widget/SimpleMonthView_Delegate.java b/tools/layoutlib/bridge/src/android/widget/SimpleMonthView_Delegate.java new file mode 100644 index 000000000000..8e41e5162513 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/widget/SimpleMonthView_Delegate.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015 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.widget; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.icu.text.SimpleDateFormat; +import android.text.format.DateFormat; + +import java.util.Calendar; +import java.util.Locale; + +/** + * Delegate that provides implementation for some methods in {@link SimpleMonthView}. + *

+ * Through the layoutlib_create tool, selected methods of SimpleMonthView have been replaced by + * calls to methods of the same name in this delegate class. + *

+ * The main purpose of this class is to use {@link android.icu.text.SimpleDateFormat} instead of + * {@link java.text.SimpleDateFormat}. + */ +public class SimpleMonthView_Delegate { + + private static final String DEFAULT_TITLE_FORMAT = "MMMMy"; + private static final String DAY_OF_WEEK_FORMAT = "EEEEE"; + + // Maintain a cache of the last view used, so that the formatters can be reused. + @Nullable private static SimpleMonthView sLastView; + @Nullable private static SimpleMonthView_Delegate sLastDelegate; + + private SimpleDateFormat mTitleFormatter; + private SimpleDateFormat mDayOfWeekFormatter; + + private Locale locale; + + @LayoutlibDelegate + /*package*/ static CharSequence getTitle(SimpleMonthView view) { + if (view.mTitle == null) { + SimpleMonthView_Delegate delegate = getDelegate(view); + if (delegate.mTitleFormatter == null) { + delegate.mTitleFormatter = new SimpleDateFormat(DateFormat.getBestDateTimePattern( + getLocale(delegate, view), DEFAULT_TITLE_FORMAT)); + } + view.mTitle = delegate.mTitleFormatter.format(view.mCalendar.getTime()); + } + return view.mTitle; + } + + @LayoutlibDelegate + /*package*/ static String getDayOfWeekLabel(SimpleMonthView view, int dayOfWeek) { + view.mDayOfWeekLabelCalendar.set(Calendar.DAY_OF_WEEK, dayOfWeek); + SimpleMonthView_Delegate delegate = getDelegate(view); + if (delegate.mDayOfWeekFormatter == null) { + delegate.mDayOfWeekFormatter = + new SimpleDateFormat(DAY_OF_WEEK_FORMAT, getLocale(delegate, view)); + } + return delegate.mDayOfWeekFormatter.format(view.mDayOfWeekLabelCalendar.getTime()); + } + + private static Locale getLocale(SimpleMonthView_Delegate delegate, SimpleMonthView view) { + if (delegate.locale == null) { + delegate.locale = view.getContext().getResources().getConfiguration().locale; + } + return delegate.locale; + } + + @NonNull + private static SimpleMonthView_Delegate getDelegate(SimpleMonthView view) { + if (view == sLastView) { + assert sLastDelegate != null; + return sLastDelegate; + } else { + sLastView = view; + sLastDelegate = new SimpleMonthView_Delegate(); + return sLastDelegate; + } + } + + public static void clearCache() { + sLastView = null; + sLastDelegate = null; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java index a833ebe8015e..92b39e3f1b78 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java @@ -36,6 +36,7 @@ import android.util.DisplayMetrics; import android.view.ViewConfiguration_Accessor; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager_Accessor; +import android.widget.SimpleMonthView_Delegate; import java.util.Locale; import java.util.concurrent.TimeUnit; @@ -277,6 +278,7 @@ public abstract class RenderAction extends FrameworkReso mContext.getRenderResources().setLogger(null); } ParserFactory.setParserFactory(null); + SimpleMonthView_Delegate.clearCache(); } public static BridgeContext getCurrentContext() { diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java index f6c2626e4271..8f0ad01c6dc3 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java @@ -77,6 +77,8 @@ public class AsmGenerator { /** Methods to inject. FQCN of class in which method should be injected => runnable that does * the injection. */ private final Map mInjectedMethodsMap; + /** A map { FQCN => set { field names } } which should be promoted to public visibility */ + private final Map> mPromotedFields; /** * Creates a new generator that can generate the output JAR with the stubbed classes. @@ -109,20 +111,8 @@ public class AsmGenerator { // Create the map/set of methods to change to delegates mDelegateMethods = new HashMap>(); - for (String signature : createInfo.getDelegateMethods()) { - int pos = signature.indexOf('#'); - if (pos <= 0 || pos >= signature.length() - 1) { - continue; - } - String className = binaryToInternalClassName(signature.substring(0, pos)); - String methodName = signature.substring(pos + 1); - Set methods = mDelegateMethods.get(className); - if (methods == null) { - methods = new HashSet(); - mDelegateMethods.put(className, methods); - } - methods.add(methodName); - } + addToMap(createInfo.getDelegateMethods(), mDelegateMethods); + for (String className : createInfo.getDelegateClassNatives()) { className = binaryToInternalClassName(className); Set methods = mDelegateMethods.get(className); @@ -187,10 +177,34 @@ public class AsmGenerator { returnTypes.add(binaryToInternalClassName(className)); } + mPromotedFields = new HashMap>(); + addToMap(createInfo.getPromotedFields(), mPromotedFields); + mInjectedMethodsMap = createInfo.getInjectedMethodsMap(); } /** + * For each value in the array, split the value on '#' and add the parts to the map as key + * and value. + */ + private void addToMap(String[] entries, Map> map) { + for (String entry : entries) { + int pos = entry.indexOf('#'); + if (pos <= 0 || pos >= entry.length() - 1) { + return; + } + String className = binaryToInternalClassName(entry.substring(0, pos)); + String methodOrFieldName = entry.substring(pos + 1); + Set set = map.get(className); + if (set == null) { + set = new HashSet(); + map.put(className, set); + } + set.add(methodOrFieldName); + } + } + + /** * Returns the list of classes that have not been renamed yet. *

* The names are "internal class names" rather than FQCN, i.e. they use "/" instead "." @@ -380,6 +394,10 @@ public class AsmGenerator { } } + Set promoteFields = mPromotedFields.get(className); + if (promoteFields != null && !promoteFields.isEmpty()) { + cv = new PromoteFieldClassAdapter(cv, promoteFields); + } cr.accept(cv, 0); return cw.toByteArray(); } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 499bea42007f..484240f49b20 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -120,6 +120,11 @@ public final class CreateInfo implements ICreateInfo { } @Override + public String[] getPromotedFields() { + return PROMOTED_FIELDS; + } + + @Override public Map getInjectedMethodsMap() { return INJECTED_METHODS; } @@ -185,6 +190,8 @@ public final class CreateInfo implements ICreateInfo { "android.view.RenderNode#nSetElevation", "android.view.RenderNode#nGetElevation", "android.view.ViewGroup#drawChild", + "android.widget.SimpleMonthView#getTitle", + "android.widget.SimpleMonthView#getDayOfWeekLabel", "android.widget.TimePickerClockDelegate#getAmOrPmKeyCode", "com.android.internal.view.menu.MenuBuilder#createNewMenuItem", "com.android.internal.util.XmlUtils#convertValueToInt", @@ -289,6 +296,12 @@ public final class CreateInfo implements ICreateInfo { "org.kxml2.io.KXmlParser" }; + private final static String[] PROMOTED_FIELDS = new String[] { + "android.widget.SimpleMonthView#mTitle", + "android.widget.SimpleMonthView#mCalendar", + "android.widget.SimpleMonthView#mDayOfWeekLabelCalendar" + }; + /** * List of classes for which the methods returning them should be deleted. * The array contains a list of null terminated section starting with the name of the class diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java index 54b1fe628769..6c62423a2a89 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java @@ -78,6 +78,13 @@ public interface ICreateInfo { Set getExcludedClasses(); /** + * Returns a list of fields which should be promoted to public visibility. 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[] getPromotedFields(); + + /** * Returns a map from binary FQCN className to {@link InjectMethodRunnable} which will be * called to inject methods into a class. * Can be empty but must not be null. diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java new file mode 100644 index 000000000000..e4b70da2504f --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 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_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_PROTECTED; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ASM4; + +/** + * Promotes given fields to public visibility. + */ +public class PromoteFieldClassAdapter extends ClassVisitor { + + private final Set mFieldNames; + private static final int ACC_NOT_PUBLIC = ~(ACC_PRIVATE | ACC_PROTECTED); + + public PromoteFieldClassAdapter(ClassVisitor cv, Set fieldNames) { + super(ASM4, cv); + mFieldNames = fieldNames; + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, + Object value) { + if (mFieldNames.contains(name)) { + if ((access & ACC_PUBLIC) == 0) { + access = (access & ACC_NOT_PUBLIC) | ACC_PUBLIC; + } + } + return super.visitField(access, name, desc, signature, value); + } +} diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java index 2c21470d6a2f..8a2235b8526c 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java @@ -138,6 +138,11 @@ public class AsmGeneratorTest { } @Override + public String[] getPromotedFields() { + return new String[0]; + } + + @Override public Map getInjectedMethodsMap() { return new HashMap(0); } @@ -213,6 +218,11 @@ public class AsmGeneratorTest { } @Override + public String[] getPromotedFields() { + return new String[0]; + } + + @Override public Map getInjectedMethodsMap() { return new HashMap(0); } @@ -296,6 +306,11 @@ public class AsmGeneratorTest { } @Override + public String[] getPromotedFields() { + return new String[0]; + } + + @Override public Map getInjectedMethodsMap() { return new HashMap(0); } @@ -374,6 +389,11 @@ public class AsmGeneratorTest { } @Override + public String[] getPromotedFields() { + return new String[0]; + } + + @Override public Map getInjectedMethodsMap() { HashMap map = new HashMap(1); -- 2.11.0