diff options
26 files changed, 1000 insertions, 720 deletions
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 4e2ff0bdb54f..a904460a0073 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -17,7 +17,6 @@ package android.app; import android.app.ActivityManager.StackInfo; -import android.app.ProfilerInfo; import android.content.ComponentName; import android.content.IIntentReceiver; import android.content.IIntentSender; @@ -2189,17 +2188,12 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } - case GET_ACTIVITY_CONTAINER_TRANSACTION: { + case GET_ACTIVITY_DISPLAY_ID_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder activityToken = data.readStrongBinder(); - IActivityContainer activityContainer = getEnclosingActivityContainer(activityToken); + int displayId = getActivityDisplayId(activityToken); reply.writeNoException(); - if (activityContainer != null) { - reply.writeInt(1); - reply.writeStrongBinder(activityContainer.asBinder()); - } else { - reply.writeInt(0); - } + reply.writeInt(displayId); return true; } @@ -5169,26 +5163,21 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } - public IActivityContainer getEnclosingActivityContainer(IBinder activityToken) - throws RemoteException { + @Override + public int getActivityDisplayId(IBinder activityToken) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(activityToken); - mRemote.transact(GET_ACTIVITY_CONTAINER_TRANSACTION, data, reply, 0); + mRemote.transact(GET_ACTIVITY_DISPLAY_ID_TRANSACTION, data, reply, 0); reply.readException(); - final int result = reply.readInt(); - final IActivityContainer res; - if (result == 1) { - res = IActivityContainer.Stub.asInterface(reply.readStrongBinder()); - } else { - res = null; - } + final int displayId = reply.readInt(); data.recycle(); reply.recycle(); - return res; + return displayId; } + @Override public IBinder getHomeActivityToken() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index dd49009281f3..647566a3904b 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2312,10 +2312,7 @@ public final class ActivityThread { final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); try { - IActivityContainer container = - ActivityManagerNative.getDefault().getEnclosingActivityContainer(r.token); - final int displayId = - container == null ? Display.DEFAULT_DISPLAY : container.getDisplayId(); + int displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token); if (displayId > Display.DEFAULT_DISPLAY) { Display display = dm.getRealDisplay(displayId, r.token); baseContext = appContext.createDisplayContext(display); @@ -2323,6 +2320,7 @@ public final class ActivityThread { } catch (RemoteException e) { } + // For debugging purposes, if the activity's package name contains the value of // the "debug.use-second-display" system property as a substring, then show // its content on a secondary display if there is one. diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index be26f3033459..1a8785b77141 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -435,8 +435,7 @@ public interface IActivityManager extends IInterface { public void deleteActivityContainer(IActivityContainer container) throws RemoteException; - public IActivityContainer getEnclosingActivityContainer(IBinder activityToken) - throws RemoteException; + public int getActivityDisplayId(IBinder activityToken) throws RemoteException; public IBinder getHomeActivityToken() throws RemoteException; @@ -746,7 +745,7 @@ public interface IActivityManager extends IInterface { int GET_PERSISTED_URI_PERMISSIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+181; int APP_NOT_RESPONDING_VIA_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+182; int GET_HOME_ACTIVITY_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+183; - int GET_ACTIVITY_CONTAINER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+184; + int GET_ACTIVITY_DISPLAY_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+184; int DELETE_ACTIVITY_CONTAINER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+185; diff --git a/core/java/android/content/pm/ParceledListSlice.java b/core/java/android/content/pm/ParceledListSlice.java index 8a43472a430b..335a45e40777 100644 --- a/core/java/android/content/pm/ParceledListSlice.java +++ b/core/java/android/content/pm/ParceledListSlice.java @@ -30,6 +30,12 @@ import java.util.List; * Transfer a large list of Parcelable objects across an IPC. Splits into * multiple transactions if needed. * + * Caveat: for efficiency and security, all elements must be the same concrete type. + * In order to avoid writing the class name of each object, we must ensure that + * each object is the same type, or else unparceling then reparceling the data may yield + * a different result if the class name encoded in the Parcelable is a Base type. + * See b/17671747. + * * @hide */ public class ParceledListSlice<T extends Parcelable> implements Parcelable { @@ -56,13 +62,25 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable { if (N <= 0) { return; } + Parcelable.Creator<T> creator = p.readParcelableCreator(loader); + Class<?> listElementClass = null; + int i = 0; while (i < N) { if (p.readInt() == 0) { break; } - mList.add(p.readCreator(creator, loader)); + + final T parcelable = p.readCreator(creator, loader); + if (listElementClass == null) { + listElementClass = parcelable.getClass(); + } else { + verifySameType(listElementClass, parcelable.getClass()); + } + + mList.add(parcelable); + if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1)); i++; } @@ -82,7 +100,11 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable { return; } while (i < N && reply.readInt() != 0) { - mList.add(reply.readCreator(creator, loader)); + final T parcelable = reply.readCreator(creator, loader); + verifySameType(listElementClass, parcelable.getClass()); + + mList.add(parcelable); + if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1)); i++; } @@ -91,6 +113,14 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable { } } + private static void verifySameType(final Class<?> expected, final Class<?> actual) { + if (!actual.equals(expected)) { + throw new IllegalArgumentException("Can't unparcel type " + + actual.getName() + " in list of type " + + expected.getName()); + } + } + public List<T> getList() { return mList; } @@ -116,11 +146,16 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable { dest.writeInt(N); if (DEBUG) Log.d(TAG, "Writing " + N + " items"); if (N > 0) { + final Class<?> listElementClass = mList.get(0).getClass(); dest.writeParcelableCreator(mList.get(0)); int i = 0; while (i < N && dest.dataSize() < MAX_FIRST_IPC_SIZE) { dest.writeInt(1); - mList.get(i).writeToParcel(dest, callFlags); + + final T parcelable = mList.get(i); + verifySameType(listElementClass, parcelable.getClass()); + parcelable.writeToParcel(dest, callFlags); + if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i)); i++; } @@ -137,7 +172,11 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable { if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N); while (i < N && reply.dataSize() < MAX_IPC_SIZE) { reply.writeInt(1); - mList.get(i).writeToParcel(reply, callFlags); + + final T parcelable = mList.get(i); + verifySameType(listElementClass, parcelable.getClass()); + parcelable.writeToParcel(reply, callFlags); + if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i)); i++; } diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index d7b75dbb0324..41d1eb2985ea 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -610,7 +610,12 @@ jbyteArray GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, return NULL; } - const size_t size = bitmap->getSize(); + const int64_t size64 = bitmap->computeSize64(); + if (!sk_64_isS32(size64)) { + doThrowIAE(env, "bitmap size exceeds 32 bits"); + return NULL; + } + const size_t size = sk_64_asS32(size64); jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(gVMRuntime, gVMRuntime_newNonMovableArray, gByte_class, size); diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp index 1dafa1b5397d..d99ddeb9bfc1 100644 --- a/core/jni/android/graphics/NinePatchPeeker.cpp +++ b/core/jni/android/graphics/NinePatchPeeker.cpp @@ -24,7 +24,9 @@ bool NinePatchPeeker::peek(const char tag[], const void* data, size_t length) { if (!strcmp("npTc", tag) && length >= sizeof(Res_png_9patch)) { Res_png_9patch* patch = (Res_png_9patch*) data; size_t patchSize = patch->serializedSize(); - assert(length == patchSize); + if (length != patchSize) { + return false; + } // You have to copy the data because it is owned by the png reader Res_png_9patch* patchNew = (Res_png_9patch*) malloc(patchSize); memcpy(patchNew, patch, patchSize); diff --git a/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java b/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java new file mode 100644 index 000000000000..e5a92bf23bdc --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java @@ -0,0 +1,263 @@ +package android.content.pm; + +import android.os.Parcel; +import android.os.Parcelable; +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.List; + +public class ParceledListSliceTest extends TestCase { + + public void testSmallList() throws Exception { + final int objectCount = 100; + List<SmallObject> list = new ArrayList<SmallObject>(); + for (int i = 0; i < objectCount; i++) { + list.add(new SmallObject(i * 2, (i * 2) + 1)); + } + + ParceledListSlice<SmallObject> slice; + + Parcel parcel = Parcel.obtain(); + try { + parcel.writeParcelable(new ParceledListSlice<SmallObject>(list), 0); + parcel.setDataPosition(0); + slice = parcel.readParcelable(getClass().getClassLoader()); + } finally { + parcel.recycle(); + } + + assertNotNull(slice); + assertNotNull(slice.getList()); + assertEquals(objectCount, slice.getList().size()); + + for (int i = 0; i < objectCount; i++) { + assertEquals(i * 2, slice.getList().get(i).mFieldA); + assertEquals((i * 2) + 1, slice.getList().get(i).mFieldB); + } + } + + private static int measureLargeObject() { + Parcel p = Parcel.obtain(); + try { + new LargeObject(0, 0, 0, 0, 0).writeToParcel(p, 0); + return p.dataPosition(); + } finally { + p.recycle(); + } + } + + /** + * Test that when the list is large, the data is successfully parceled + * and unparceled (the implementation will send pieces of the list in + * separate round-trips to avoid the IPC limit). + */ + public void testLargeList() throws Exception { + final int thresholdBytes = 256 * 1024; + final int objectCount = thresholdBytes / measureLargeObject(); + + List<LargeObject> list = new ArrayList<LargeObject>(); + for (int i = 0; i < objectCount; i++) { + list.add(new LargeObject( + i * 5, + (i * 5) + 1, + (i * 5) + 2, + (i * 5) + 3, + (i * 5) + 4 + )); + } + + ParceledListSlice<LargeObject> slice; + + Parcel parcel = Parcel.obtain(); + try { + parcel.writeParcelable(new ParceledListSlice<LargeObject>(list), 0); + parcel.setDataPosition(0); + slice = parcel.readParcelable(getClass().getClassLoader()); + } finally { + parcel.recycle(); + } + + assertNotNull(slice); + assertNotNull(slice.getList()); + assertEquals(objectCount, slice.getList().size()); + + for (int i = 0; i < objectCount; i++) { + assertEquals(i * 5, slice.getList().get(i).mFieldA); + assertEquals((i * 5) + 1, slice.getList().get(i).mFieldB); + assertEquals((i * 5) + 2, slice.getList().get(i).mFieldC); + assertEquals((i * 5) + 3, slice.getList().get(i).mFieldD); + assertEquals((i * 5) + 4, slice.getList().get(i).mFieldE); + } + } + + /** + * Test that only homogeneous elements may be unparceled. + */ + public void testHomogeneousElements() throws Exception { + List<BaseObject> list = new ArrayList<BaseObject>(); + list.add(new LargeObject(0, 1, 2, 3, 4)); + list.add(new SmallObject(5, 6)); + list.add(new SmallObject(7, 8)); + + Parcel parcel = Parcel.obtain(); + try { + writeEvilParceledListSlice(parcel, list); + parcel.setDataPosition(0); + try { + ParceledListSlice.CREATOR.createFromParcel(parcel, getClass().getClassLoader()); + assertTrue("Unparceled heterogeneous ParceledListSlice", false); + } catch (IllegalArgumentException e) { + // Success, we're not allowed to process heterogeneous + // elements in a ParceledListSlice. + } + } finally { + parcel.recycle(); + } + } + + /** + * Write a ParcelableListSlice that uses the BaseObject base class as the Creator. + * This is dangerous, as it may affect how the data is unparceled, then later parceled + * by the system, leading to a self-modifying data security vulnerability. + */ + private static <T extends BaseObject> void writeEvilParceledListSlice(Parcel dest, List<T> list) { + final int listCount = list.size(); + + // Number of items. + dest.writeInt(listCount); + + // The type/creator to use when unparceling. Here we use the base class + // to simulate an attack on ParceledListSlice. + dest.writeString(BaseObject.class.getName()); + + for (int i = 0; i < listCount; i++) { + // 1 means the item is present. + dest.writeInt(1); + list.get(i).writeToParcel(dest, 0); + } + } + + public abstract static class BaseObject implements Parcelable { + protected static final int TYPE_SMALL = 0; + protected static final int TYPE_LARGE = 1; + + protected void writeToParcel(Parcel dest, int flags, int type) { + dest.writeInt(type); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * This is *REALLY* bad, but we're doing it in the test to ensure that we handle + * the possible exploit when unparceling an object with the BaseObject written as + * Creator. + */ + public static final Creator<BaseObject> CREATOR = new Creator<BaseObject>() { + @Override + public BaseObject createFromParcel(Parcel source) { + switch (source.readInt()) { + case TYPE_SMALL: + return SmallObject.createFromParcelBody(source); + case TYPE_LARGE: + return LargeObject.createFromParcelBody(source); + default: + throw new IllegalArgumentException("Unknown type"); + } + } + + @Override + public BaseObject[] newArray(int size) { + return new BaseObject[size]; + } + }; + } + + public static class SmallObject extends BaseObject { + public int mFieldA; + public int mFieldB; + + public SmallObject(int a, int b) { + mFieldA = a; + mFieldB = b; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags, TYPE_SMALL); + dest.writeInt(mFieldA); + dest.writeInt(mFieldB); + } + + public static SmallObject createFromParcelBody(Parcel source) { + return new SmallObject(source.readInt(), source.readInt()); + } + + public static final Creator<SmallObject> CREATOR = new Creator<SmallObject>() { + @Override + public SmallObject createFromParcel(Parcel source) { + // Consume the type (as it is always written out). + source.readInt(); + return createFromParcelBody(source); + } + + @Override + public SmallObject[] newArray(int size) { + return new SmallObject[size]; + } + }; + } + + public static class LargeObject extends BaseObject { + public int mFieldA; + public int mFieldB; + public int mFieldC; + public int mFieldD; + public int mFieldE; + + public LargeObject(int a, int b, int c, int d, int e) { + mFieldA = a; + mFieldB = b; + mFieldC = c; + mFieldD = d; + mFieldE = e; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags, TYPE_LARGE); + dest.writeInt(mFieldA); + dest.writeInt(mFieldB); + dest.writeInt(mFieldC); + dest.writeInt(mFieldD); + dest.writeInt(mFieldE); + } + + public static LargeObject createFromParcelBody(Parcel source) { + return new LargeObject( + source.readInt(), + source.readInt(), + source.readInt(), + source.readInt(), + source.readInt() + ); + } + + public static final Creator<LargeObject> CREATOR = new Creator<LargeObject>() { + @Override + public LargeObject createFromParcel(Parcel source) { + // Consume the type (as it is always written out). + source.readInt(); + return createFromParcelBody(source); + } + + @Override + public LargeObject[] newArray(int size) { + return new LargeObject[size]; + } + }; + } +} diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h index ac5eca08a4ff..07df5ae00252 100644 --- a/include/androidfw/ResourceTypes.h +++ b/include/androidfw/ResourceTypes.h @@ -107,9 +107,9 @@ struct Res_png_9patch yDivsOffset(0), colorsOffset(0) { } int8_t wasDeserialized; - int8_t numXDivs; - int8_t numYDivs; - int8_t numColors; + uint8_t numXDivs; + uint8_t numYDivs; + uint8_t numColors; // The offset (from the start of this structure) to the xDivs & yDivs // array for this 9patch. To get a pointer to this array, call diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 8dfb3217c0af..3215144ac59a 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -55,6 +55,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseIntArray; +import android.view.Display; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsService; @@ -8721,14 +8722,13 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override - public IActivityContainer getEnclosingActivityContainer(IBinder activityToken) - throws RemoteException { + public int getActivityDisplayId(IBinder activityToken) throws RemoteException { synchronized (this) { ActivityStack stack = ActivityRecord.getStackLocked(activityToken); - if (stack != null) { - return stack.mActivityContainer; + if (stack != null && stack.mActivityContainer.isAttachedLocked()) { + return stack.mActivityContainer.getDisplayId(); } - return null; + return Display.DEFAULT_DISPLAY; } } diff --git a/tools/layoutlib/.idea/codeStyleSettings.xml b/tools/layoutlib/.idea/codeStyleSettings.xml index a04e4405e6b2..89f7b341fb8b 100644 --- a/tools/layoutlib/.idea/codeStyleSettings.xml +++ b/tools/layoutlib/.idea/codeStyleSettings.xml @@ -5,7 +5,6 @@ <value> <option name="FIELD_NAME_PREFIX" value="m" /> <option name="STATIC_FIELD_NAME_PREFIX" value="s" /> - <option name="USE_FQ_CLASS_NAMES_IN_JAVADOC" value="false" /> <option name="INSERT_INNER_CLASS_IMPORTS" value="true" /> <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" /> <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" /> @@ -34,11 +33,13 @@ <option name="JD_ADD_BLANK_AFTER_RETURN" value="true" /> <option name="JD_DO_NOT_WRAP_ONE_LINE_COMMENTS" value="true" /> <option name="WRAP_COMMENTS" value="true" /> + <JavaCodeStyleSettings> + <option name="CLASS_NAMES_IN_JAVADOC" value="3" /> + </JavaCodeStyleSettings> <XML> <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" /> </XML> <codeStyleSettings language="JAVA"> - <option name="INDENT_CASE_FROM_SWITCH" value="false" /> <option name="ALIGN_MULTILINE_PARAMETERS" value="false" /> <option name="CALL_PARAMETERS_WRAP" value="1" /> <option name="METHOD_PARAMETERS_WRAP" value="1" /> diff --git a/tools/layoutlib/.idea/misc.xml b/tools/layoutlib/.idea/misc.xml index fd63e6c66f2d..fa48f70ac6a1 100644 --- a/tools/layoutlib/.idea/misc.xml +++ b/tools/layoutlib/.idea/misc.xml @@ -12,5 +12,4 @@ <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/out" /> </component> -</project> - +</project>
\ No newline at end of file diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java index c41a4ee08d4f..2691e5640ff0 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java @@ -16,12 +16,13 @@ package android.content.res; +import com.android.ide.common.rendering.api.AssetRepository; import com.android.layoutlib.bridge.Bridge; -import android.content.res.AssetManager; - public class BridgeAssetManager extends AssetManager { + private AssetRepository mAssetRepository; + /** * This initializes the static field {@link AssetManager#sSystem} which is used * by methods who get a global asset manager using {@link AssetManager#getSystem()}. @@ -48,6 +49,14 @@ public class BridgeAssetManager extends AssetManager { AssetManager.sSystem = null; } - private BridgeAssetManager() { + public void setAssetRepository(AssetRepository assetRepository) { + mAssetRepository = assetRepository; + } + + public AssetRepository getAssetRepository() { + return mAssetRepository; + } + + public BridgeAssetManager() { } } diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java index 66126af47663..96ca25059afa 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java @@ -178,11 +178,21 @@ public final class BridgeResources extends Resources { Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { + ResourceValue resourceValue = value.getSecond(); try { - return ResourceHelper.getColor(value.getSecond().getValue()); + return ResourceHelper.getColor(resourceValue.getValue()); } catch (NumberFormatException e) { - Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, - null /*data*/); + // Check if the value passed is a file. If it is, mostly likely, user is referencing + // a color state list from a place where they should reference only a pure color. + String message; + if (new File(resourceValue.getValue()).isFile()) { + String resource = (resourceValue.isFramework() ? "@android:" : "@") + "color/" + + resourceValue.getName(); + message = "Hexadecimal color expected, found Color State List for " + resource; + } else { + message = e.getMessage(); + } + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, message, e, null); return 0; } } diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java index a2bd6d7840d0..18036927363f 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java @@ -614,15 +614,27 @@ public final class BridgeTypedArray extends TypedArray { int pos = value.indexOf('/'); String idName = value.substring(pos + 1); - - // if this is a framework id - if (mPlatformFile || value.startsWith("@android") || value.startsWith("@+android")) { - // look for idName in the android R classes + boolean create = value.startsWith("@+"); + boolean isFrameworkId = + mPlatformFile || value.startsWith("@android") || value.startsWith("@+android"); + + // Look for the idName in project or android R class depending on isPlatform. + if (create) { + Integer idValue; + if (isFrameworkId) { + idValue = Bridge.getResourceId(ResourceType.ID, idName); + } else { + idValue = mContext.getProjectCallback().getResourceId(ResourceType.ID, idName); + } + return idValue == null ? defValue : idValue; + } + // This calls the same method as in if(create), but doesn't create a dynamic id, if + // one is not found. + if (isFrameworkId) { return mContext.getFrameworkResourceValue(ResourceType.ID, idName, defValue); + } else { + return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue); } - - // look for idName in the project R class. - return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue); } // not a direct id valid reference? resolve it diff --git a/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java b/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java index a3ec2ccd013b..b9928fce6c93 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java +++ b/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java @@ -29,75 +29,38 @@ import java.awt.image.WritableRaster; * The class is adapted from a demo tool for Blending Modes written by * Romain Guy (romainguy@android.com). The tool is available at * http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/ + * + * This class has been adapted for applying color filters. When applying color filters, the src + * image should not extend beyond the dest image, but in our implementation of the filters, it does. + * To compensate for the effect, we recompute the alpha value of the src image before applying + * the color filter as it should have been applied. */ public final class BlendComposite implements Composite { public enum BlendingMode { - NORMAL, - AVERAGE, - MULTIPLY, - SCREEN, - DARKEN, - LIGHTEN, - OVERLAY, - HARD_LIGHT, - SOFT_LIGHT, - DIFFERENCE, - NEGATION, - EXCLUSION, - COLOR_DODGE, - INVERSE_COLOR_DODGE, - SOFT_DODGE, - COLOR_BURN, - INVERSE_COLOR_BURN, - SOFT_BURN, - REFLECT, - GLOW, - FREEZE, - HEAT, - ADD, - SUBTRACT, - STAMP, - RED, - GREEN, - BLUE, - HUE, - SATURATION, - COLOR, - LUMINOSITY + MULTIPLY(Multiply), + SCREEN(Screen), + DARKEN(Darken), + LIGHTEN(Lighten), + OVERLAY(Overlay), + ADD(Add); + + private BlendComposite mComposite; + + BlendingMode(BlendComposite composite) { + mComposite = composite; + } + + BlendComposite getBlendComposite() { + return mComposite; + } } - public static final BlendComposite Normal = new BlendComposite(BlendingMode.NORMAL); - public static final BlendComposite Average = new BlendComposite(BlendingMode.AVERAGE); public static final BlendComposite Multiply = new BlendComposite(BlendingMode.MULTIPLY); public static final BlendComposite Screen = new BlendComposite(BlendingMode.SCREEN); public static final BlendComposite Darken = new BlendComposite(BlendingMode.DARKEN); public static final BlendComposite Lighten = new BlendComposite(BlendingMode.LIGHTEN); public static final BlendComposite Overlay = new BlendComposite(BlendingMode.OVERLAY); - public static final BlendComposite HardLight = new BlendComposite(BlendingMode.HARD_LIGHT); - public static final BlendComposite SoftLight = new BlendComposite(BlendingMode.SOFT_LIGHT); - public static final BlendComposite Difference = new BlendComposite(BlendingMode.DIFFERENCE); - public static final BlendComposite Negation = new BlendComposite(BlendingMode.NEGATION); - public static final BlendComposite Exclusion = new BlendComposite(BlendingMode.EXCLUSION); - public static final BlendComposite ColorDodge = new BlendComposite(BlendingMode.COLOR_DODGE); - public static final BlendComposite InverseColorDodge = new BlendComposite(BlendingMode.INVERSE_COLOR_DODGE); - public static final BlendComposite SoftDodge = new BlendComposite(BlendingMode.SOFT_DODGE); - public static final BlendComposite ColorBurn = new BlendComposite(BlendingMode.COLOR_BURN); - public static final BlendComposite InverseColorBurn = new BlendComposite(BlendingMode.INVERSE_COLOR_BURN); - public static final BlendComposite SoftBurn = new BlendComposite(BlendingMode.SOFT_BURN); - public static final BlendComposite Reflect = new BlendComposite(BlendingMode.REFLECT); - public static final BlendComposite Glow = new BlendComposite(BlendingMode.GLOW); - public static final BlendComposite Freeze = new BlendComposite(BlendingMode.FREEZE); - public static final BlendComposite Heat = new BlendComposite(BlendingMode.HEAT); public static final BlendComposite Add = new BlendComposite(BlendingMode.ADD); - public static final BlendComposite Subtract = new BlendComposite(BlendingMode.SUBTRACT); - public static final BlendComposite Stamp = new BlendComposite(BlendingMode.STAMP); - public static final BlendComposite Red = new BlendComposite(BlendingMode.RED); - public static final BlendComposite Green = new BlendComposite(BlendingMode.GREEN); - public static final BlendComposite Blue = new BlendComposite(BlendingMode.BLUE); - public static final BlendComposite Hue = new BlendComposite(BlendingMode.HUE); - public static final BlendComposite Saturation = new BlendComposite(BlendingMode.SATURATION); - public static final BlendComposite Color = new BlendComposite(BlendingMode.COLOR); - public static final BlendComposite Luminosity = new BlendComposite(BlendingMode.LUMINOSITY); private float alpha; private BlendingMode mode; @@ -112,21 +75,16 @@ public final class BlendComposite implements Composite { } public static BlendComposite getInstance(BlendingMode mode) { - return new BlendComposite(mode); + return mode.getBlendComposite(); } public static BlendComposite getInstance(BlendingMode mode, float alpha) { + if (alpha > 0.9999f) { + return getInstance(mode); + } return new BlendComposite(mode, alpha); } - public BlendComposite derive(BlendingMode mode) { - return this.mode == mode ? this : new BlendComposite(mode, getAlpha()); - } - - public BlendComposite derive(float alpha) { - return this.alpha == alpha ? this : new BlendComposite(getMode(), alpha); - } - public float getAlpha() { return alpha; } @@ -157,11 +115,7 @@ public final class BlendComposite implements Composite { BlendComposite bc = (BlendComposite) obj; - if (mode != bc.mode) { - return false; - } - - return alpha == bc.alpha; + return mode == bc.mode && alpha == bc.alpha; } public CompositeContext createContext(ColorModel srcColorModel, @@ -220,6 +174,11 @@ public final class BlendComposite implements Composite { dstPixel[2] = (pixel ) & 0xFF; dstPixel[3] = (pixel >> 24) & 0xFF; + // ---- Modified from original ---- + // recompute src pixel for transparency. + srcPixel[3] *= dstPixel[3] / 0xFF; + // ---- Modification ends ---- + result = blender.blend(srcPixel, dstPixel, result); // mixes the result with the opacity @@ -246,123 +205,8 @@ public final class BlendComposite implements Composite { private static abstract class Blender { public abstract int[] blend(int[] src, int[] dst, int[] result); - private static void RGBtoHSL(int r, int g, int b, float[] hsl) { - float var_R = (r / 255f); - float var_G = (g / 255f); - float var_B = (b / 255f); - - float var_Min; - float var_Max; - float del_Max; - - if (var_R > var_G) { - var_Min = var_G; - var_Max = var_R; - } else { - var_Min = var_R; - var_Max = var_G; - } - if (var_B > var_Max) { - var_Max = var_B; - } - if (var_B < var_Min) { - var_Min = var_B; - } - - del_Max = var_Max - var_Min; - - float H, S, L; - L = (var_Max + var_Min) / 2f; - - if (del_Max - 0.01f <= 0.0f) { - H = 0; - S = 0; - } else { - if (L < 0.5f) { - S = del_Max / (var_Max + var_Min); - } else { - S = del_Max / (2 - var_Max - var_Min); - } - - float del_R = (((var_Max - var_R) / 6f) + (del_Max / 2f)) / del_Max; - float del_G = (((var_Max - var_G) / 6f) + (del_Max / 2f)) / del_Max; - float del_B = (((var_Max - var_B) / 6f) + (del_Max / 2f)) / del_Max; - - if (var_R == var_Max) { - H = del_B - del_G; - } else if (var_G == var_Max) { - H = (1 / 3f) + del_R - del_B; - } else { - H = (2 / 3f) + del_G - del_R; - } - if (H < 0) { - H += 1; - } - if (H > 1) { - H -= 1; - } - } - - hsl[0] = H; - hsl[1] = S; - hsl[2] = L; - } - - private static void HSLtoRGB(float h, float s, float l, int[] rgb) { - int R, G, B; - - if (s - 0.01f <= 0.0f) { - R = (int) (l * 255.0f); - G = (int) (l * 255.0f); - B = (int) (l * 255.0f); - } else { - float var_1, var_2; - if (l < 0.5f) { - var_2 = l * (1 + s); - } else { - var_2 = (l + s) - (s * l); - } - var_1 = 2 * l - var_2; - - R = (int) (255.0f * hue2RGB(var_1, var_2, h + (1.0f / 3.0f))); - G = (int) (255.0f * hue2RGB(var_1, var_2, h)); - B = (int) (255.0f * hue2RGB(var_1, var_2, h - (1.0f / 3.0f))); - } - - rgb[0] = R; - rgb[1] = G; - rgb[2] = B; - } - - private static float hue2RGB(float v1, float v2, float vH) { - if (vH < 0.0f) { - vH += 1.0f; - } - if (vH > 1.0f) { - vH -= 1.0f; - } - if ((6.0f * vH) < 1.0f) { - return (v1 + (v2 - v1) * 6.0f * vH); - } - if ((2.0f * vH) < 1.0f) { - return (v2); - } - if ((3.0f * vH) < 2.0f) { - return (v1 + (v2 - v1) * ((2.0f / 3.0f) - vH) * 6.0f); - } - return (v1); - } - public static Blender getBlenderFor(BlendComposite composite) { switch (composite.getMode()) { - case NORMAL: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - System.arraycopy(src, 0, result, 0, 4); - return result; - } - }; case ADD: return new Blender() { @Override @@ -373,65 +217,6 @@ public final class BlendComposite implements Composite { return result; } }; - case AVERAGE: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - for (int i = 0; i < 3; i++) { - result[i] = (src[i] + dst[i]) >> 1; - } - result[3] = Math.min(255, src[3] + dst[3]); - return result; - } - }; - case BLUE: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - System.arraycopy(dst, 0, result, 0, 3); - result[3] = Math.min(255, src[3] + dst[3]); - return result; - } - }; - case COLOR: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - float[] srcHSL = new float[3]; - RGBtoHSL(src[0], src[1], src[2], srcHSL); - float[] dstHSL = new float[3]; - RGBtoHSL(dst[0], dst[1], dst[2], dstHSL); - - HSLtoRGB(srcHSL[0], srcHSL[1], dstHSL[2], result); - result[3] = Math.min(255, src[3] + dst[3]); - - return result; - } - }; - case COLOR_BURN: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - for (int i = 0; i < 3; i++) { - result[i] = src[i] == 0 ? 0 : - Math.max(0, 255 - (((255 - dst[i]) << 8) / src[i])); - } - result[3] = Math.min(255, src[3] + dst[3]); - return result; - } - }; - case COLOR_DODGE: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - for (int i = 0; i < 3; i++) { - result[i] = src[i] == 255 ? 255 : - Math.min((dst[i] << 8) / (255 - src[i]), 255); - } - result[3] = Math.min(255, src[3] + dst[3]); - return result; - } - }; case DARKEN: return new Blender() { @Override @@ -443,136 +228,6 @@ public final class BlendComposite implements Composite { return result; } }; - case DIFFERENCE: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - for (int i = 0; i < 3; i++) { - result[i] = dst[i] + src[i] - (dst[i] * src[i] >> 7); - } - result[3] = Math.min(255, src[3] + dst[3]); - return result; - } - }; - case EXCLUSION: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - for (int i = 0; i < 3; i++) { - result[i] = dst[i] + src[i] - (dst[i] * src[i] >> 7); - } - result[3] = Math.min(255, src[3] + dst[3]); - return result; - } - }; - case FREEZE: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - for (int i = 0; i < 3; i++) { - result[i] = src[i] == 0 ? 0 : - Math.max(0, 255 - (255 - dst[i]) * (255 - dst[i]) / src[i]); - } - result[3] = Math.min(255, src[3] + dst[3]); - return result; - } - }; - case GLOW: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - for (int i = 0; i < 3; i++) { - result[i] = dst[i] == 255 ? 255 : - Math.min(255, src[i] * src[i] / (255 - dst[i])); - } - result[3] = Math.min(255, src[3] + dst[3]); - return result; - } - }; - case GREEN: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - return new int[] { - dst[0], - dst[1], - src[2], - Math.min(255, src[3] + dst[3]) - }; - } - }; - case HARD_LIGHT: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - return new int[] { - src[0] < 128 ? dst[0] * src[0] >> 7 : - 255 - ((255 - src[0]) * (255 - dst[0]) >> 7), - src[1] < 128 ? dst[1] * src[1] >> 7 : - 255 - ((255 - src[1]) * (255 - dst[1]) >> 7), - src[2] < 128 ? dst[2] * src[2] >> 7 : - 255 - ((255 - src[2]) * (255 - dst[2]) >> 7), - Math.min(255, src[3] + dst[3]) - }; - } - }; - case HEAT: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - return new int[] { - dst[0] == 0 ? 0 : Math.max(0, 255 - (255 - src[0]) * (255 - src[0]) / dst[0]), - dst[1] == 0 ? 0 : Math.max(0, 255 - (255 - src[1]) * (255 - src[1]) / dst[1]), - dst[2] == 0 ? 0 : Math.max(0, 255 - (255 - src[2]) * (255 - src[2]) / dst[2]), - Math.min(255, src[3] + dst[3]) - }; - } - }; - case HUE: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - float[] srcHSL = new float[3]; - RGBtoHSL(src[0], src[1], src[2], srcHSL); - float[] dstHSL = new float[3]; - RGBtoHSL(dst[0], dst[1], dst[2], dstHSL); - - HSLtoRGB(srcHSL[0], dstHSL[1], dstHSL[2], result); - result[3] = Math.min(255, src[3] + dst[3]); - - return result; - } - }; - case INVERSE_COLOR_BURN: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - return new int[] { - dst[0] == 0 ? 0 : - Math.max(0, 255 - (((255 - src[0]) << 8) / dst[0])), - dst[1] == 0 ? 0 : - Math.max(0, 255 - (((255 - src[1]) << 8) / dst[1])), - dst[2] == 0 ? 0 : - Math.max(0, 255 - (((255 - src[2]) << 8) / dst[2])), - Math.min(255, src[3] + dst[3]) - }; - } - }; - case INVERSE_COLOR_DODGE: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - return new int[] { - dst[0] == 255 ? 255 : - Math.min((src[0] << 8) / (255 - dst[0]), 255), - dst[1] == 255 ? 255 : - Math.min((src[1] << 8) / (255 - dst[1]), 255), - dst[2] == 255 ? 255 : - Math.min((src[2] << 8) / (255 - dst[2]), 255), - Math.min(255, src[3] + dst[3]) - }; - } - }; case LIGHTEN: return new Blender() { @Override @@ -584,21 +239,6 @@ public final class BlendComposite implements Composite { return result; } }; - case LUMINOSITY: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - float[] srcHSL = new float[3]; - RGBtoHSL(src[0], src[1], src[2], srcHSL); - float[] dstHSL = new float[3]; - RGBtoHSL(dst[0], dst[1], dst[2], dstHSL); - - HSLtoRGB(dstHSL[0], dstHSL[1], srcHSL[2], result); - result[3] = Math.min(255, src[3] + dst[3]); - - return result; - } - }; case MULTIPLY: return new Blender() { @Override @@ -606,22 +246,10 @@ public final class BlendComposite implements Composite { for (int i = 0; i < 3; i++) { result[i] = (src[i] * dst[i]) >> 8; } - result[3] = Math.min(255, src[3] + dst[3]); + result[3] = Math.min(255, src[3] + dst[3] - (src[3] * dst[3]) / 255); return result; } }; - case NEGATION: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - return new int[] { - 255 - Math.abs(255 - dst[0] - src[0]), - 255 - Math.abs(255 - dst[1] - src[1]), - 255 - Math.abs(255 - dst[2] - src[2]), - Math.min(255, src[3] + dst[3]) - }; - } - }; case OVERLAY: return new Blender() { @Override @@ -634,125 +262,17 @@ public final class BlendComposite implements Composite { return result; } }; - case RED: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - return new int[] { - src[0], - dst[1], - dst[2], - Math.min(255, src[3] + dst[3]) - }; - } - }; - case REFLECT: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - return new int[] { - src[0] == 255 ? 255 : Math.min(255, dst[0] * dst[0] / (255 - src[0])), - src[1] == 255 ? 255 : Math.min(255, dst[1] * dst[1] / (255 - src[1])), - src[2] == 255 ? 255 : Math.min(255, dst[2] * dst[2] / (255 - src[2])), - Math.min(255, src[3] + dst[3]) - }; - } - }; - case SATURATION: + case SCREEN: return new Blender() { @Override public int[] blend(int[] src, int[] dst, int[] result) { - float[] srcHSL = new float[3]; - RGBtoHSL(src[0], src[1], src[2], srcHSL); - float[] dstHSL = new float[3]; - RGBtoHSL(dst[0], dst[1], dst[2], dstHSL); - - HSLtoRGB(dstHSL[0], srcHSL[1], dstHSL[2], result); + result[0] = 255 - ((255 - src[0]) * (255 - dst[0]) >> 8); + result[1] = 255 - ((255 - src[1]) * (255 - dst[1]) >> 8); + result[2] = 255 - ((255 - src[2]) * (255 - dst[2]) >> 8); result[3] = Math.min(255, src[3] + dst[3]); - return result; } }; - case SCREEN: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - return new int[] { - 255 - ((255 - src[0]) * (255 - dst[0]) >> 8), - 255 - ((255 - src[1]) * (255 - dst[1]) >> 8), - 255 - ((255 - src[2]) * (255 - dst[2]) >> 8), - Math.min(255, src[3] + dst[3]) - }; - } - }; - case SOFT_BURN: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - return new int[] { - dst[0] + src[0] < 256 ? - (dst[0] == 255 ? 255 : - Math.min(255, (src[0] << 7) / (255 - dst[0]))) : - Math.max(0, 255 - (((255 - dst[0]) << 7) / src[0])), - dst[1] + src[1] < 256 ? - (dst[1] == 255 ? 255 : - Math.min(255, (src[1] << 7) / (255 - dst[1]))) : - Math.max(0, 255 - (((255 - dst[1]) << 7) / src[1])), - dst[2] + src[2] < 256 ? - (dst[2] == 255 ? 255 : - Math.min(255, (src[2] << 7) / (255 - dst[2]))) : - Math.max(0, 255 - (((255 - dst[2]) << 7) / src[2])), - Math.min(255, src[3] + dst[3]) - }; - } - }; - case SOFT_DODGE: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - return new int[] { - dst[0] + src[0] < 256 ? - (src[0] == 255 ? 255 : - Math.min(255, (dst[0] << 7) / (255 - src[0]))) : - Math.max(0, 255 - (((255 - src[0]) << 7) / dst[0])), - dst[1] + src[1] < 256 ? - (src[1] == 255 ? 255 : - Math.min(255, (dst[1] << 7) / (255 - src[1]))) : - Math.max(0, 255 - (((255 - src[1]) << 7) / dst[1])), - dst[2] + src[2] < 256 ? - (src[2] == 255 ? 255 : - Math.min(255, (dst[2] << 7) / (255 - src[2]))) : - Math.max(0, 255 - (((255 - src[2]) << 7) / dst[2])), - Math.min(255, src[3] + dst[3]) - }; - } - }; - case SOFT_LIGHT: - break; - case STAMP: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - return new int[] { - Math.max(0, Math.min(255, dst[0] + 2 * src[0] - 256)), - Math.max(0, Math.min(255, dst[1] + 2 * src[1] - 256)), - Math.max(0, Math.min(255, dst[2] + 2 * src[2] - 256)), - Math.min(255, src[3] + dst[3]) - }; - } - }; - case SUBTRACT: - return new Blender() { - @Override - public int[] blend(int[] src, int[] dst, int[] result) { - return new int[] { - Math.max(0, src[0] + dst[0] - 256), - Math.max(0, src[1] + dst[1] - 256), - Math.max(0, src[2] + dst[2] - 256), - Math.min(255, src[3] + dst[3]) - }; - } - }; } throw new IllegalArgumentException("Blender not implement for " + composite.getMode().name()); diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java index ab79664d840d..42de4ec8e03a 100644 --- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java @@ -18,21 +18,28 @@ package android.graphics; import com.android.annotations.NonNull; import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.AssetRepository; import com.android.ide.common.rendering.api.LayoutLog; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.content.res.AssetManager; +import android.content.res.BridgeAssetManager; import java.awt.Font; import java.awt.FontFormatException; import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Scanner; import java.util.Set; @@ -56,10 +63,28 @@ public class FontFamily_Delegate { public static final int BOLD_FONT_WEIGHT_DELTA = 300; public static final int BOLD_FONT_WEIGHT = 700; - // FONT_SUFFIX_ITALIC will always match FONT_SUFFIX_BOLDITALIC and hence it must be checked - // separately. private static final String FONT_SUFFIX_ITALIC = "Italic.ttf"; private static final String FN_ALL_FONTS_LIST = "fontsInSdk.txt"; + private static final String EXTENSION_OTF = ".otf"; + + private static final int CACHE_SIZE = 10; + // The cache has a drawback that if the font file changed after the font object was created, + // we will not update it. + private static final Map<String, FontInfo> sCache = + new LinkedHashMap<String, FontInfo>(CACHE_SIZE) { + @Override + protected boolean removeEldestEntry(Entry<String, FontInfo> eldest) { + return size() > CACHE_SIZE; + } + + @Override + public FontInfo put(String key, FontInfo value) { + // renew this entry. + FontInfo removed = remove(key); + super.put(key, value); + return removed; + } + }; /** * A class associating {@link Font} with its metadata. @@ -194,7 +219,7 @@ public class FontFamily_Delegate { try { return Font.createFont(Font.TRUETYPE_FONT, f); } catch (Exception e) { - if (path.endsWith(".otf") && e instanceof FontFormatException) { + if (path.endsWith(EXTENSION_OTF) && e instanceof FontFormatException) { // If we aren't able to load an Open Type font, don't log a warning just yet. // We wait for a case where font is being used. Only then we try to log the // warning. @@ -281,8 +306,74 @@ public class FontFamily_Delegate { @LayoutlibDelegate /*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Typeface.createFromAsset is not supported.", null, null); + FontFamily_Delegate ffd = sManager.getDelegate(nativeFamily); + ffd.mValid = true; + if (mgr == null) { + return false; + } + if (mgr instanceof BridgeAssetManager) { + InputStream fontStream = null; + try { + AssetRepository assetRepository = ((BridgeAssetManager) mgr).getAssetRepository(); + if (assetRepository == null) { + Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET, "Asset not found: " + path, + null); + return false; + } + if (!assetRepository.isSupported()) { + // Don't log any warnings on unsupported IDEs. + return false; + } + // Check cache + FontInfo fontInfo = sCache.get(path); + if (fontInfo != null) { + // renew the font's lease. + sCache.put(path, fontInfo); + ffd.addFont(fontInfo); + return true; + } + fontStream = assetRepository.openAsset(path, AssetManager.ACCESS_STREAMING); + if (fontStream == null) { + Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET, "Asset not found: " + path, + path); + return false; + } + Font font = Font.createFont(Font.TRUETYPE_FONT, fontStream); + fontInfo = new FontInfo(); + fontInfo.mFont = font; + fontInfo.mWeight = font.isBold() ? BOLD_FONT_WEIGHT : DEFAULT_FONT_WEIGHT; + fontInfo.mIsItalic = font.isItalic(); + ffd.addFont(fontInfo); + return true; + } catch (IOException e) { + Bridge.getLog().error(LayoutLog.TAG_MISSING_ASSET, "Unable to load font " + path, e, + path); + } catch (FontFormatException e) { + if (path.endsWith(EXTENSION_OTF)) { + // otf fonts are not supported on the user's config (JRE version + OS) + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "OpenType fonts are not supported yet: " + path, null, path); + } else { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Unable to load font " + path, e, path); + } + } finally { + if (fontStream != null) { + try { + fontStream.close(); + } catch (IOException ignored) { + } + } + } + return false; + } + // This should never happen. AssetManager is a final class (from user's perspective), and + // we've replaced every creation of AssetManager with our implementation. We create an + // exception and log it, but continue with rest of the rendering, without loading this font. + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "You have found a bug in the rendering library. Please file a bug at b.android.com.", + new RuntimeException("Asset Manager is not an instance of BridgeAssetManager"), + null); return false; } diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java index 4ac376cb326f..1ca94dc7fa84 100644 --- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java @@ -103,7 +103,7 @@ public class PorterDuffColorFilter_Delegate extends ColorFilter_Delegate { // For filtering the colors, the src image should contain the "color" only for pixel values // which are not transparent in the target image. But, we are using a simple rectangular image // completely filled with color. Hence some Composite rules do not apply as intended. However, - // in such cases, they can usually be mapped to some other mode, which produces an + // in such cases, they can usually be mapped to some other mode, which produces an approximately // equivalent result. private Mode getCompatibleMode(Mode mode) { Mode m = mode; diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java index 36102f1123e0..7e4ff6983315 100644 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java @@ -179,7 +179,7 @@ public final class BridgeInflater extends LayoutInflater { XmlPullParser parser = ParserFactory.create(f); BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( - parser, bridgeContext, false); + parser, bridgeContext, value.isFramework()); return inflate(bridgeParser, root); } catch (Exception e) { diff --git a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java index a6c00f77a50a..391504682c72 100644 --- a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java @@ -71,8 +71,10 @@ public class ViewGroup_Delegate { int x = 0; if (outline.mRect != null) { Shadow s = getRectShadow(parent, canvas, child, outline); - shadow = s.mShadow; - x = -s.mShadowWidth; + if (s != null) { + shadow = s.mShadow; + x = -s.mShadowWidth; + } } else if (outline.mPath != null) { shadow = getPathShadow(child, outline, canvas); } @@ -132,6 +134,9 @@ public class ViewGroup_Delegate { private static BufferedImage getPathShadow(View child, Outline outline, Canvas canvas) { Rect clipBounds = canvas.getClipBounds(); + if (clipBounds.isEmpty()) { + return null; + } BufferedImage image = new BufferedImage(clipBounds.width(), clipBounds.height(), BufferedImage.TYPE_INT_ARGB); Graphics2D graphics = image.createGraphics(); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index 8523f1a95dab..7c86f755c176 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -17,6 +17,7 @@ package com.android.layoutlib.bridge.android; import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.AssetRepository; import com.android.ide.common.rendering.api.ILayoutPullParser; import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.LayoutLog; @@ -47,6 +48,7 @@ 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.BridgeResources; import android.content.res.BridgeTypedArray; import android.content.res.Configuration; @@ -101,6 +103,7 @@ public final class BridgeContext extends Context { * used to populate the mViewKeyMap. */ private final HashMap<Object, Object> mViewKeyHelpMap = new HashMap<Object, Object>(); + private final BridgeAssetManager mAssets; private Resources mSystemResources; private final Object mProjectKey; private final DisplayMetrics mMetrics; @@ -140,6 +143,7 @@ public final class BridgeContext extends Context { */ public BridgeContext(Object projectKey, DisplayMetrics metrics, RenderResources renderResources, + AssetRepository assets, IProjectCallback projectCallback, Configuration config, int targetSdkVersion, @@ -150,6 +154,8 @@ public final class BridgeContext extends Context { mRenderResources = renderResources; mConfig = config; + mAssets = new BridgeAssetManager(); + mAssets.setAssetRepository(assets); mApplicationInfo = new ApplicationInfo(); mApplicationInfo.targetSdkVersion = targetSdkVersion; @@ -288,6 +294,11 @@ public final class BridgeContext extends Context { 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. @@ -295,7 +306,6 @@ public final class BridgeContext extends Context { return true; } - int a; // if this is a framework value. if (value.isFramework()) { @@ -1071,9 +1081,8 @@ public final class BridgeContext extends Context { } @Override - public AssetManager getAssets() { - // pass - return null; + public BridgeAssetManager getAssets() { + return mAssets; } @Override diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java new file mode 100644 index 000000000000..e5023b873e23 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java @@ -0,0 +1,166 @@ +/* + * 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.layoutlib.bridge.bars; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.SessionParams; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.impl.ResourceHelper; +import com.android.resources.ResourceType; + +import android.graphics.drawable.Drawable; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + + +/** + * Assumes that the AppCompat library is present in the project's classpath and creates an + * actionbar around it. + */ +public class AppCompatActionBar extends BridgeActionBar { + + private Object mWindowDecorActionBar; + private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar"; + private Class<?> mWindowActionBarClass; + + /** + * Inflate the action bar and attach it to {@code parentView} + */ + public AppCompatActionBar(@NonNull BridgeContext context, @NonNull SessionParams params, + @NonNull ViewGroup parentView) { + super(context, params, parentView); + int contentRootId = context.getProjectResourceValue(ResourceType.ID, + "action_bar_activity_content", 0); + View contentView = getDecorContent().findViewById(contentRootId); + if (contentView != null) { + assert contentView instanceof FrameLayout; + setContentRoot(((FrameLayout) contentView)); + } else { + // Something went wrong. Create a new FrameLayout in the enclosing layout. + FrameLayout contentRoot = new FrameLayout(context); + setMatchParent(contentRoot); + mEnclosingLayout.addView(contentRoot); + setContentRoot(contentRoot); + } + try { + Class[] constructorParams = {View.class}; + Object[] constructorArgs = {getDecorContent()}; + mWindowDecorActionBar = params.getProjectCallback().loadView(WINDOW_ACTION_BAR_CLASS, + constructorParams, constructorArgs); + + mWindowActionBarClass = mWindowDecorActionBar == null ? null : + mWindowDecorActionBar.getClass(); + setupActionBar(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + protected ResourceValue getLayoutResource(BridgeContext context) { + // We always assume that the app has requested the action bar. + return context.getRenderResources().getProjectResource(ResourceType.LAYOUT, + "abc_screen_toolbar"); + } + + @Override + protected void setTitle(CharSequence title) { + if (title != null && mWindowDecorActionBar != null) { + Method setTitle = getMethod(mWindowActionBarClass, "setTitle", CharSequence.class); + invoke(setTitle, mWindowDecorActionBar, title); + } + } + + @Override + protected void setSubtitle(CharSequence subtitle) { + if (subtitle != null && mWindowDecorActionBar != null) { + Method setSubtitle = getMethod(mWindowActionBarClass, "setSubtitle", CharSequence.class); + invoke(setSubtitle, mWindowDecorActionBar, subtitle); + } + } + + @Override + protected void setIcon(String icon) { + // Do this only if the action bar doesn't already have an icon. + if (icon != null && !icon.isEmpty() && mWindowDecorActionBar != null) { + if (((Boolean) invoke(getMethod(mWindowActionBarClass, "hasIcon"), mWindowDecorActionBar) + )) { + Drawable iconDrawable = getDrawable(icon, false); + if (iconDrawable != null) { + Method setIcon = getMethod(mWindowActionBarClass, "setIcon", Drawable.class); + invoke(setIcon, mWindowDecorActionBar, iconDrawable); + } + } + } + } + + @Override + protected void setHomeAsUp(boolean homeAsUp) { + if (mWindowDecorActionBar != null) { + Method setHomeAsUp = getMethod(mWindowActionBarClass, + "setDefaultDisplayHomeAsUpEnabled", boolean.class); + invoke(setHomeAsUp, mWindowDecorActionBar, homeAsUp); + } + } + + @Override + public void createMenuPopup() { + // it's hard to addd menus to appcompat's actionbar, since it'll use a lot of reflection. + // so we skip it for now. + } + + @Nullable + private static Method getMethod(Class<?> owner, String name, Class<?>... parameterTypes) { + try { + return owner == null ? null : owner.getMethod(name, parameterTypes); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + return null; + } + + @Nullable + private static Object invoke(Method method, Object owner, Object... args) { + try { + return method == null ? null : method.invoke(owner, args); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + + // TODO: this is duplicated from FrameworkActionBarWrapper$WindowActionBarWrapper + @Nullable + private Drawable getDrawable(@NonNull String name, boolean isFramework) { + RenderResources res = mBridgeContext.getRenderResources(); + ResourceValue value = res.findResValue(name, isFramework); + value = res.resolveResValue(value); + if (value != null) { + return ResourceHelper.getDrawable(value, mBridgeContext); + } + return null; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java new file mode 100644 index 000000000000..b29d25f78d65 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java @@ -0,0 +1,159 @@ +/* + * 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.layoutlib.bridge.bars; + +import com.android.annotations.NonNull; +import com.android.ide.common.rendering.api.ActionBarCallback; +import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.SessionParams; +import com.android.layoutlib.bridge.android.BridgeContext; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.widget.FrameLayout; +import android.widget.RelativeLayout; + +/** + * An abstraction over two implementations of the ActionBar - framework and appcompat. + */ +public abstract class BridgeActionBar { + // Store a reference to the context so that we don't have to cast it repeatedly. + @NonNull protected final BridgeContext mBridgeContext; + @NonNull protected final SessionParams mParams; + // A Layout that contains the inflated action bar. The menu popup is added to this layout. + @NonNull protected final ViewGroup mEnclosingLayout; + + private final View mDecorContent; + private final ActionBarCallback mCallback; + + @NonNull private FrameLayout mContentRoot; + + public BridgeActionBar(@NonNull BridgeContext context, @NonNull SessionParams params, + @NonNull ViewGroup parentView) { + mBridgeContext = context; + mParams = params; + mCallback = params.getProjectCallback().getActionBarCallback(); + ResourceValue layoutName = getLayoutResource(context); + if (layoutName == null) { + throw new RuntimeException("Unable to find the layout for Action Bar."); + } + int layoutId; + if (layoutName.isFramework()) { + layoutId = context.getFrameworkResourceValue(layoutName.getResourceType(), + layoutName.getName(), 0); + } else { + layoutId = context.getProjectResourceValue(layoutName.getResourceType(), + layoutName.getName(), 0); + + } + if (layoutId == 0) { + throw new RuntimeException( + String.format("Unable to resolve attribute \"%1$s\" of type \"%2$s\"", + layoutName.getName(), layoutName.getResourceType())); + } + if (mCallback.isOverflowPopupNeeded()) { + // Create a RelativeLayout around the action bar, to which the overflow popup may be + // added. + mEnclosingLayout = new RelativeLayout(mBridgeContext); + setMatchParent(mEnclosingLayout); + parentView.addView(mEnclosingLayout); + } else { + mEnclosingLayout = parentView; + } + + // Inflate action bar layout. + mDecorContent = LayoutInflater.from(context).inflate(layoutId, mEnclosingLayout, true); + + } + + /** + * Returns the Layout Resource that should be used to inflate the action bar. This layout + * should cover the complete screen, and have a FrameLayout included, where the content will + * be inflated. + */ + protected abstract ResourceValue getLayoutResource(BridgeContext context); + + protected void setContentRoot(FrameLayout contentRoot) { + mContentRoot = contentRoot; + } + + @NonNull + public FrameLayout getContentRoot() { + return mContentRoot; + } + + /** + * Returns the view inflated. This should contain both the ActionBar and the app content in it. + */ + protected View getDecorContent() { + return mDecorContent; + } + + /** Setup things like the title, subtitle, icon etc. */ + protected void setupActionBar() { + setTitle(); + setSutTitle(); + setIcon(); + setHomeAsUp(mCallback.getHomeButtonStyle() == HomeButtonStyle.SHOW_HOME_AS_UP); + } + + protected abstract void setTitle(CharSequence title); + protected abstract void setSubtitle(CharSequence subtitle); + protected abstract void setIcon(String icon); + protected abstract void setHomeAsUp(boolean homeAsUp); + + private void setTitle() { + RenderResources res = mBridgeContext.getRenderResources(); + + String title = mParams.getAppLabel(); + ResourceValue titleValue = res.findResValue(title, false); + if (titleValue != null && titleValue.getValue() != null) { + setTitle(titleValue.getValue()); + } else { + setTitle(title); + } + } + + private void setSutTitle() { + String subTitle = mCallback.getSubTitle(); + if (subTitle != null) { + setSubtitle(subTitle); + } + } + + private void setIcon() { + String appIcon = mParams.getAppIcon(); + if (appIcon != null) { + setIcon(appIcon); + } + } + + public abstract void createMenuPopup(); + + public ActionBarCallback getCallBack() { + return mCallback; + } + + protected static void setMatchParent(View view) { + view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT)); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java index 2ff8d3739fa9..a1c90651cbf0 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java @@ -31,7 +31,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.util.DisplayMetrics; import android.util.TypedValue; -import android.view.LayoutInflater; +import android.view.InflateException; import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewGroup; @@ -44,70 +44,30 @@ import android.widget.RelativeLayout; import java.util.ArrayList; -public class ActionBarLayout { +/** + * Creates the ActionBar as done by the framework. + */ +public class FrameworkActionBar extends BridgeActionBar { private static final String LAYOUT_ATTR_NAME = "windowActionBarFullscreenDecorLayout"; // The Action Bar - @NonNull - private CustomActionBarWrapper mActionBar; - - // Store another reference to the context so that we don't have to cast it repeatedly. - @NonNull - private final BridgeContext mBridgeContext; - - @NonNull - private FrameLayout mContentRoot; + @NonNull private FrameworkActionBarWrapper mActionBar; // A fake parent for measuring views. - @Nullable - private ViewGroup mMeasureParent; - - // A Layout that contains the inflated action bar. The menu popup is added to this layout. - @NonNull - private final RelativeLayout mEnclosingLayout; + @Nullable private ViewGroup mMeasureParent; /** * Inflate the action bar and attach it to {@code parentView} */ - public ActionBarLayout(@NonNull BridgeContext context, @NonNull SessionParams params, + public FrameworkActionBar(@NonNull BridgeContext context, @NonNull SessionParams params, @NonNull ViewGroup parentView) { + super(context, params, parentView); - mBridgeContext = context; - - ResourceValue layoutName = context.getRenderResources() - .findItemInTheme(LAYOUT_ATTR_NAME, true); - if (layoutName != null) { - // We may need to resolve the reference obtained. - layoutName = context.getRenderResources().findResValue(layoutName.getValue(), - layoutName.isFramework()); - } - int layoutId = 0; - String error = null; - if (layoutName == null) { - error = "Unable to find action bar layout (" + LAYOUT_ATTR_NAME - + ") in the current theme."; - } else { - layoutId = context.getFrameworkResourceValue(layoutName.getResourceType(), - layoutName.getName(), 0); - if (layoutId == 0) { - error = String.format("Unable to resolve attribute \"%s\" of type \"%s\"", - layoutName.getName(), layoutName.getResourceType()); - } - } - if (layoutId == 0) { - throw new RuntimeException(error); - } - // Create a RelativeLayout to hold the action bar. The layout is needed so that we may - // add the menu popup to it. - mEnclosingLayout = new RelativeLayout(mBridgeContext); - setMatchParent(mEnclosingLayout); - parentView.addView(mEnclosingLayout); - - // Inflate action bar layout. - View decorContent = LayoutInflater.from(context).inflate(layoutId, mEnclosingLayout, true); + View decorContent = getDecorContent(); - mActionBar = CustomActionBarWrapper.getActionBarWrapper(context, params, decorContent); + mActionBar = FrameworkActionBarWrapper.getActionBarWrapper(context, getCallBack(), + decorContent); FrameLayout contentRoot = (FrameLayout) mEnclosingLayout.findViewById(android.R.id.content); @@ -117,27 +77,62 @@ public class ActionBarLayout { contentRoot = new FrameLayout(context); setMatchParent(contentRoot); mEnclosingLayout.addView(contentRoot); - mContentRoot = contentRoot; + setContentRoot(contentRoot); } else { - mContentRoot = contentRoot; - mActionBar.setupActionBar(); + setContentRoot(contentRoot); + setupActionBar(); mActionBar.inflateMenus(); } } - private void setMatchParent(View view) { - view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT)); + @Override + protected ResourceValue getLayoutResource(BridgeContext context) { + ResourceValue layoutName = + context.getRenderResources().findItemInTheme(LAYOUT_ATTR_NAME, true); + if (layoutName != null) { + // We may need to resolve the reference obtained. + layoutName = context.getRenderResources().findResValue(layoutName.getValue(), + layoutName.isFramework()); + } + if (layoutName == null) { + throw new InflateException("Unable to find action bar layout (" + LAYOUT_ATTR_NAME + + ") in the current theme."); + } + return layoutName; + } + + @Override + protected void setupActionBar() { + super.setupActionBar(); + mActionBar.setupActionBar(); + } + + @Override + protected void setHomeAsUp(boolean homeAsUp) { + mActionBar.setHomeAsUp(homeAsUp); + } + + @Override + protected void setTitle(CharSequence title) { + mActionBar.setTitle(title); + } + + @Override + protected void setSubtitle(CharSequence subtitle) { + mActionBar.setSubTitle(subtitle); + } + + @Override + protected void setIcon(String icon) { + mActionBar.setIcon(icon); } /** * Creates a Popup and adds it to the content frame. It also adds another {@link FrameLayout} to * the content frame which shall serve as the new content root. */ + @Override public void createMenuPopup() { - assert mEnclosingLayout.getChildCount() == 1 - : "Action Bar Menus have already been created."; - if (!isOverflowPopupNeeded()) { return; } @@ -193,11 +188,6 @@ public class ActionBarLayout { return needed; } - @NonNull - public FrameLayout getContentRoot() { - return mContentRoot; - } - // Copied from com.android.internal.view.menu.MenuPopHelper.measureContentWidth() private int measureContentWidth(@NonNull ListAdapter adapter) { // Menus don't tend to be long, so this is more sane than it looks. diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java index 6db722ec680d..44c2cd85c8cd 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java @@ -19,10 +19,8 @@ package com.android.layoutlib.bridge.bars; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.ide.common.rendering.api.ActionBarCallback; -import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.ResourceValue; -import com.android.ide.common.rendering.api.SessionParams; import com.android.internal.R; import com.android.internal.app.ToolbarActionBar; import com.android.internal.app.WindowDecorActionBar; @@ -54,10 +52,9 @@ import static com.android.resources.ResourceType.MENU; /** * A common API to access {@link ToolbarActionBar} and {@link WindowDecorActionBar}. */ -public abstract class CustomActionBarWrapper { +public abstract class FrameworkActionBarWrapper { @NonNull protected ActionBar mActionBar; - @NonNull protected SessionParams mParams; @NonNull protected ActionBarCallback mCallback; @NonNull protected BridgeContext mContext; @@ -68,49 +65,48 @@ public abstract class CustomActionBarWrapper { * ?attr/windowActionBarFullscreenDecorLayout */ @NonNull - public static CustomActionBarWrapper getActionBarWrapper(@NonNull BridgeContext context, - @NonNull SessionParams params, @NonNull View decorContent) { + public static FrameworkActionBarWrapper getActionBarWrapper(@NonNull BridgeContext context, + @NonNull ActionBarCallback callback, @NonNull View decorContent) { View view = decorContent.findViewById(R.id.action_bar); if (view instanceof Toolbar) { - return new ToolbarWrapper(context, params, ((Toolbar) view)); + return new ToolbarWrapper(context, callback, (Toolbar) view); } else if (view instanceof ActionBarView) { - return new WindowActionBarWrapper(context, params, decorContent, - ((ActionBarView) view)); + return new WindowActionBarWrapper(context, callback, decorContent, + (ActionBarView) view); } else { throw new IllegalStateException("Can't make an action bar out of " + view.getClass().getSimpleName()); } } - CustomActionBarWrapper(@NonNull BridgeContext context, @NonNull SessionParams params, + FrameworkActionBarWrapper(@NonNull BridgeContext context, ActionBarCallback callback, @NonNull ActionBar actionBar) { mActionBar = actionBar; - mParams = params; - mCallback = params.getProjectCallback().getActionBarCallback(); + mCallback = callback; mContext = context; } + /** A call to setup any custom properties. */ protected void setupActionBar() { - // Do the things that are common to all implementations. - RenderResources res = mContext.getRenderResources(); + // Nothing to do here. + } - String title = mParams.getAppLabel(); - ResourceValue titleValue = res.findResValue(title, false); - if (titleValue != null && titleValue.getValue() != null) { - mActionBar.setTitle(titleValue.getValue()); - } else { - mActionBar.setTitle(title); - } + public void setTitle(CharSequence title) { + mActionBar.setTitle(title); + } - String subTitle = mCallback.getSubTitle(); + public void setSubTitle(CharSequence subTitle) { if (subTitle != null) { mActionBar.setSubtitle(subTitle); } + } - // Add show home as up icon. - if (mCallback.getHomeButtonStyle() == HomeButtonStyle.SHOW_HOME_AS_UP) { - mActionBar.setDisplayOptions(0xFF, ActionBar.DISPLAY_HOME_AS_UP); - } + public void setHomeAsUp(boolean homeAsUp) { + mActionBar.setDisplayHomeAsUpEnabled(homeAsUp); + } + + public void setIcon(String icon) { + // Nothing to do. } protected boolean isSplit() { @@ -186,15 +182,14 @@ public abstract class CustomActionBarWrapper { * Material theme uses {@link Toolbar} as the action bar. This wrapper provides access to * Toolbar using a common API. */ - private static class ToolbarWrapper extends CustomActionBarWrapper { + private static class ToolbarWrapper extends FrameworkActionBarWrapper { @NonNull private final Toolbar mToolbar; // This is the view. - ToolbarWrapper(@NonNull BridgeContext context, @NonNull SessionParams params, + ToolbarWrapper(@NonNull BridgeContext context, @NonNull ActionBarCallback callback, @NonNull Toolbar toolbar) { - super(context, params, new ToolbarActionBar(toolbar, "", new WindowCallback()) - ); + super(context, callback, new ToolbarActionBar(toolbar, "", new WindowCallback())); mToolbar = toolbar; } @@ -248,19 +243,17 @@ public abstract class CustomActionBarWrapper { * Holo theme uses {@link WindowDecorActionBar} as the action bar. This wrapper provides * access to it using a common API. */ - private static class WindowActionBarWrapper extends CustomActionBarWrapper { + private static class WindowActionBarWrapper extends FrameworkActionBarWrapper { - @NonNull - private final WindowDecorActionBar mActionBar; - @NonNull - private final ActionBarView mActionBarView; - @NonNull - private final View mDecorContentRoot; + @NonNull private final WindowDecorActionBar mActionBar; + @NonNull private final ActionBarView mActionBarView; + @NonNull private final View mDecorContentRoot; private MenuBuilder mMenuBuilder; - public WindowActionBarWrapper(@NonNull BridgeContext context, @NonNull SessionParams params, - @NonNull View decorContentRoot, @NonNull ActionBarView actionBarView) { - super(context, params, new WindowDecorActionBar(decorContentRoot)); + public WindowActionBarWrapper(@NonNull BridgeContext context, + @NonNull ActionBarCallback callback, @NonNull View decorContentRoot, + @NonNull ActionBarView actionBarView) { + super(context, callback, new WindowDecorActionBar(decorContentRoot)); mActionBarView = actionBarView; mActionBar = ((WindowDecorActionBar) super.mActionBar); mDecorContentRoot = decorContentRoot; @@ -268,7 +261,6 @@ public abstract class CustomActionBarWrapper { @Override protected void setupActionBar() { - super.setupActionBar(); // Set the navigation mode. int navMode = mCallback.getNavigationMode(); @@ -278,16 +270,6 @@ public abstract class CustomActionBarWrapper { setupTabs(3); } - String icon = mParams.getAppIcon(); - // If the action bar style doesn't specify an icon, set the icon obtained from the - // session params. - if (!mActionBar.hasIcon() && icon != null) { - Drawable iconDrawable = getDrawable(icon, false); - if (iconDrawable != null) { - mActionBar.setIcon(iconDrawable); - } - } - // Set action bar to be split, if needed. ViewGroup splitView = (ViewGroup) mDecorContentRoot.findViewById(R.id.split_action_bar); if (splitView != null) { @@ -300,6 +282,17 @@ public abstract class CustomActionBarWrapper { } @Override + public void setIcon(String icon) { + // Set the icon only if the action bar doesn't specify an icon. + if (!mActionBar.hasIcon() && icon != null) { + Drawable iconDrawable = getDrawable(icon, false); + if (iconDrawable != null) { + mActionBar.setIcon(iconDrawable); + } + } + } + + @Override protected void inflateMenus() { super.inflateMenus(); // The super implementation doesn't set the menu on the view. Set it here. @@ -340,7 +333,7 @@ public abstract class CustomActionBarWrapper { @Override int getMenuPopupMargin() { - return -ActionBarLayout.getPixelValue("10dp", mContext.getMetrics()); + return -FrameworkActionBar.getPixelValue("10dp", mContext.getMetrics()); } // TODO: Use an adapter, like List View to set up tabs. 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 60f5331aefdb..127cb72a0440 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 @@ -35,7 +35,6 @@ import com.android.resources.ScreenSize; import android.content.res.Configuration; import android.os.HandlerThread_Delegate; -import android.os.Looper; import android.util.DisplayMetrics; import android.view.ViewConfiguration_Accessor; import android.view.inputmethod.InputMethodManager; @@ -71,7 +70,7 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso /** * Creates a renderAction. * <p> - * This <b>must</b> be followed by a call to {@link RenderAction#init()}, which act as a + * This <b>must</b> be followed by a call to {@link RenderAction#init(long)}, which act as a * call to {@link RenderAction#acquire(long)} * * @param params the RenderParams. This must be a copy that the action can keep @@ -121,8 +120,8 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso // build the context mContext = new BridgeContext(mParams.getProjectKey(), metrics, resources, - mParams.getProjectCallback(), getConfiguration(), mParams.getTargetSdkVersion(), - mParams.isRtlSupported()); + mParams.getAssets(), mParams.getProjectCallback(), getConfiguration(), + mParams.getTargetSdkVersion(), mParams.isRtlSupported()); setUp(); @@ -139,7 +138,7 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso * The preparation can fail if another rendering took too long and the timeout was elapsed. * * More than one call to this from the same thread will have no effect and will return - * {@link Result#SUCCESS}. + * {@link Result.Status#SUCCESS}. * * After scene actions have taken place, only one call to {@link #release()} must be * done. @@ -173,7 +172,7 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso * Acquire the lock so that the scene can be acted upon. * <p> * This returns null if the lock was just acquired, otherwise it returns - * {@link Result#SUCCESS} if the lock already belonged to that thread, or another + * {@link Result.Status#SUCCESS} if the lock already belonged to that thread, or another * instance (see {@link Result#getStatus()}) if an error occurred. * * @param timeout the time to wait if another rendering is happening. @@ -184,11 +183,11 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso */ private Result acquireLock(long timeout) { ReentrantLock lock = Bridge.getLock(); - if (lock.isHeldByCurrentThread() == false) { + if (!lock.isHeldByCurrentThread()) { try { boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS); - if (acquired == false) { + if (!acquired) { return ERROR_TIMEOUT.createResult(); } } catch (InterruptedException e) { @@ -308,7 +307,7 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso */ protected void checkLock() { ReentrantLock lock = Bridge.getLock(); - if (lock.isHeldByCurrentThread() == false) { + if (!lock.isHeldByCurrentThread()) { throw new IllegalStateException("scene must be acquired first. see #acquire(long)"); } if (sCurrentContext != mContext) { @@ -347,6 +346,7 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso config.screenWidthDp = hardwareConfig.getScreenWidth() / density.getDpiValue(); config.screenHeightDp = hardwareConfig.getScreenHeight() / density.getDpiValue(); if (config.screenHeightDp < config.screenWidthDp) { + //noinspection SuspiciousNameCombination config.smallestScreenWidthDp = config.screenHeightDp; } else { config.smallestScreenWidthDp = config.screenWidthDp; @@ -367,6 +367,7 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso config.orientation = Configuration.ORIENTATION_LANDSCAPE; break; case SQUARE: + //noinspection deprecation config.orientation = Configuration.ORIENTATION_SQUARE; break; } 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 4637bfd20549..58acab9aa318 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 @@ -51,11 +51,13 @@ import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.layoutlib.bridge.android.SessionParamsFlags; +import com.android.layoutlib.bridge.bars.BridgeActionBar; +import com.android.layoutlib.bridge.bars.AppCompatActionBar; import com.android.layoutlib.bridge.bars.Config; import com.android.layoutlib.bridge.bars.NavigationBar; import com.android.layoutlib.bridge.bars.StatusBar; import com.android.layoutlib.bridge.bars.TitleBar; -import com.android.layoutlib.bridge.bars.ActionBarLayout; +import com.android.layoutlib.bridge.bars.FrameworkActionBar; import com.android.layoutlib.bridge.impl.binding.FakeAdapter; import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter; import com.android.resources.Density; @@ -354,7 +356,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // if the theme says no title/action bar, then the size will be 0 if (mActionBarSize > 0) { - ActionBarLayout actionBar = createActionBar(context, params, backgroundLayout); + BridgeActionBar actionBar = createActionBar(context, params, backgroundLayout); actionBar.createMenuPopup(); mContentRoot = actionBar.getContentRoot(); } else if (mTitleBarSize > 0) { @@ -1190,8 +1192,22 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // android.support.v7.app.ActionBarActivity, and not care about the theme name at all. if (mIsThemeAppCompat == null) { StyleResourceValue defaultTheme = resources.getDefaultTheme(); - StyleResourceValue val = resources.getStyle("Theme.AppCompat", false); - mIsThemeAppCompat = defaultTheme == val || resources.themeIsParentOf(val, defaultTheme); + // 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++) { + // for loop ensures that we don't run into cyclic theme inheritance. + if (defaultTheme.getName().startsWith("Theme.AppCompat")) { + isThemeAppCompat = true; + break; + } + defaultTheme = resources.getParent(defaultTheme); + if (defaultTheme == null) { + break; + } + } + mIsThemeAppCompat = isThemeAppCompat; } return mIsThemeAppCompat; } @@ -1647,9 +1663,13 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { /** * Creates the action bar. Also queries the project callback for missing information. */ - private ActionBarLayout createActionBar(BridgeContext context, SessionParams params, + private BridgeActionBar createActionBar(BridgeContext context, SessionParams params, ViewGroup parentView) { - return new ActionBarLayout(context, params, parentView); + if (mIsThemeAppCompat == Boolean.TRUE) { + return new AppCompatActionBar(context, params, parentView); + } else { + return new FrameworkActionBar(context, params, parentView); + } } public BufferedImage getImage() { |