am e43a64e3: Merge "Changes for a quick, pre-migration bug sprint:" into lmp-docs
* commit 'e43a64e3336548094571885944afc28792e24fa0':
Changes for a quick, pre-migration bug sprint:
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 4e2ff0b..a904460 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 @@
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 @@
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 dd49009..647566a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2312,10 +2312,7 @@
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 @@
} 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 be26f30..1a8785b 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -435,8 +435,7 @@
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 @@
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 8a43472..335a45e 100644
--- a/core/java/android/content/pm/ParceledListSlice.java
+++ b/core/java/android/content/pm/ParceledListSlice.java
@@ -30,6 +30,12 @@
* 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 @@
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 @@
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 @@
}
}
+ 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 @@
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 @@
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 d7b75db..41d1eb2 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -610,7 +610,12 @@
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 1dafa1b..d99ddeb 100644
--- a/core/jni/android/graphics/NinePatchPeeker.cpp
+++ b/core/jni/android/graphics/NinePatchPeeker.cpp
@@ -24,7 +24,9 @@
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 0000000..e5a92bf
--- /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 ac5eca08..07df5ae 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -107,9 +107,9 @@
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 8dfb321..3215144 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.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 @@
}
@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 a04e440..89f7b34 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 fd63e6c..fa48f70 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 c41a4ee..2691e56 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 @@
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 66126af..96ca250 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 @@
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 a2bd6d7..18036927 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 @@
int pos = value.indexOf('/');
String idName = value.substring(pos + 1);
+ boolean create = value.startsWith("@+");
+ boolean isFrameworkId =
+ mPlatformFile || value.startsWith("@android") || value.startsWith("@+android");
- // if this is a framework id
- if (mPlatformFile || value.startsWith("@android") || value.startsWith("@+android")) {
- // look for idName in the android R classes
- return mContext.getFrameworkResourceValue(ResourceType.ID, idName, defValue);
+ // 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;
}
-
- // look for idName in the project R class.
- return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue);
+ // 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);
+ }
}
// 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 a3ec2cc..e4fcf1b 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BlendComposite.java
@@ -29,75 +29,31 @@
* 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(),
+ SCREEN(),
+ DARKEN(),
+ LIGHTEN(),
+ OVERLAY(),
+ ADD();
- 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 final BlendComposite mComposite;
+
+ BlendingMode() {
+ mComposite = new BlendComposite(this);
+ }
+
+ BlendComposite getBlendComposite() {
+ return mComposite;
+ }
+ }
private float alpha;
private BlendingMode mode;
@@ -112,21 +68,16 @@
}
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 +108,7 @@
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 +167,11 @@
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 +198,8 @@
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 +210,6 @@
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 +221,6 @@
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 +232,6 @@
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 +239,10 @@
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,123 +255,15 @@
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:
- 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[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])
- };
+ 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;
}
};
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java
index ab79664..42de4ec 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 @@
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 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 @@
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 @@
@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 4ac376c..1ca94dc 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 @@
// 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 36102f1..7ca4c47 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -35,6 +35,8 @@
import java.io.File;
+import static com.android.layoutlib.bridge.android.BridgeContext.getBaseContext;
+
/**
* Custom implementation of {@link LayoutInflater} to handle custom views.
*/
@@ -56,7 +58,12 @@
protected BridgeInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
- mProjectCallback = null;
+ newContext = getBaseContext(newContext);
+ if (newContext instanceof BridgeContext) {
+ mProjectCallback = ((BridgeContext) newContext).getProjectCallback();
+ } else {
+ mProjectCallback = null;
+ }
}
/**
@@ -151,9 +158,7 @@
@Override
public View inflate(int resource, ViewGroup root) {
Context context = getContext();
- while (context instanceof ContextThemeWrapper) {
- context = ((ContextThemeWrapper) context).getBaseContext();
- }
+ context = getBaseContext(context);
if (context instanceof BridgeContext) {
BridgeContext bridgeContext = (BridgeContext)context;
@@ -179,7 +184,7 @@
XmlPullParser parser = ParserFactory.create(f);
BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
- parser, bridgeContext, false);
+ parser, bridgeContext, value.isFramework());
return inflate(bridgeParser, root);
} catch (Exception e) {
@@ -217,9 +222,7 @@
private void setupViewInContext(View view, AttributeSet attrs) {
Context context = getContext();
- while (context instanceof ContextThemeWrapper) {
- context = ((ContextThemeWrapper) context).getBaseContext();
- }
+ context = getBaseContext(context);
if (context instanceof BridgeContext) {
BridgeContext bc = (BridgeContext) context;
// get the view key
diff --git a/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java
index dafc96b..08a97d6 100644
--- a/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java
@@ -48,9 +48,7 @@
AttributeSet attrs) {
if (menuItem instanceof BridgeMenuItemImpl) {
Context context = thisInflater.getContext();
- while (context instanceof ContextThemeWrapper) {
- context = ((ContextThemeWrapper) context).getBaseContext();
- }
+ context = BridgeContext.getBaseContext(context);
if (context instanceof BridgeContext) {
Object viewKey = BridgeInflater.getViewKeyFromParser(
attrs, ((BridgeContext) context), null, false);
diff --git a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
index a6c00f7..82ae1df 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 @@
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);
}
@@ -120,9 +122,7 @@
@NonNull
private static DisplayMetrics getMetrics(View view) {
Context context = view.getContext();
- while (context instanceof ContextThemeWrapper) {
- context = ((ContextThemeWrapper) context).getBaseContext();
- }
+ context = BridgeContext.getBaseContext(context);
if (context instanceof BridgeContext) {
return ((BridgeContext) context).getMetrics();
}
@@ -132,6 +132,9 @@
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/internal/view/menu/BridgeMenuItemImpl.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java
index 8d1d0c1..bb95c4e 100644
--- a/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java
@@ -19,7 +19,6 @@
import com.android.layoutlib.bridge.android.BridgeContext;
import android.content.Context;
-import android.view.ContextThemeWrapper;
import android.view.View;
/**
@@ -42,9 +41,7 @@
CharSequence title, int showAsAction) {
super(menu, group, id, categoryOrder, ordering, title, showAsAction);
Context context = menu.getContext();
- while (context instanceof ContextThemeWrapper) {
- context = ((ContextThemeWrapper) context).getBaseContext();
- }
+ context = BridgeContext.getBaseContext(context);
if (context instanceof BridgeContext) {
mContext = ((BridgeContext) context);
}
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 8523f1a..ef7b85b 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;
@@ -39,6 +40,7 @@
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
@@ -47,6 +49,7 @@
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 +104,7 @@
* 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 +144,7 @@
*/
public BridgeContext(Object projectKey, DisplayMetrics metrics,
RenderResources renderResources,
+ AssetRepository assets,
IProjectCallback projectCallback,
Configuration config,
int targetSdkVersion,
@@ -150,6 +155,8 @@
mRenderResources = renderResources;
mConfig = config;
+ mAssets = new BridgeAssetManager();
+ mAssets.setAssetRepository(assets);
mApplicationInfo = new ApplicationInfo();
mApplicationInfo.targetSdkVersion = targetSdkVersion;
@@ -288,6 +295,11 @@
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 +307,6 @@
return true;
}
-
int a;
// if this is a framework value.
if (value.isFramework()) {
@@ -914,6 +925,13 @@
return defValue;
}
+ public static Context getBaseContext(Context context) {
+ while (context instanceof ContextWrapper) {
+ context = ((ContextWrapper) context).getBaseContext();
+ }
+ return context;
+ }
+
//------------ NOT OVERRIDEN --------------------
@Override
@@ -1071,9 +1089,8 @@
}
@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 0000000..ee57067
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
@@ -0,0 +1,191 @@
+/*
+ * 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.ide.common.rendering.api.StyleResourceValue;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.resources.ResourceType;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+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 LayoutInflater getInflater(BridgeContext context) {
+ // Other than the resource resolution part, the code has been taken from the support
+ // library. see code from line 269 onwards in
+ // https://android.googlesource.com/platform/frameworks/support/+/android-5.1.0_r1/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
+ Context themedContext = context;
+ RenderResources resources = context.getRenderResources();
+ ResourceValue actionBarTheme = resources.findItemInTheme("actionBarTheme", false);
+ if (actionBarTheme != null) {
+ // resolve it, if needed.
+ actionBarTheme = resources.resolveResValue(actionBarTheme);
+ }
+ if (actionBarTheme instanceof StyleResourceValue) {
+ int styleId = context.getDynamicIdByStyle(((StyleResourceValue) actionBarTheme));
+ if (styleId != 0) {
+ themedContext = new ContextThemeWrapper(context, styleId);
+ }
+ }
+ return LayoutInflater.from(themedContext);
+ }
+
+ @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 (invoke(getMethod(mWindowActionBarClass, "hasIcon"), mWindowDecorActionBar)
+ == Boolean.TRUE) {
+ 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 0000000..2a83ea1
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/BridgeActionBar.java
@@ -0,0 +1,164 @@
+/*
+ * 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;
+
+ @SuppressWarnings("NullableProblems") // Should be initialized by subclasses.
+ @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 = getInflater(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 LayoutInflater getInflater(BridgeContext context) {
+ return LayoutInflater.from(context);
+ }
+
+ protected void setContentRoot(@NonNull 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
similarity index 76%
rename from tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
rename to tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBar.java
index 2ff8d37..a1c9065 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.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 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;
+ View decorContent = getDecorContent();
- 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);
-
- 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 @@
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 @@
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
similarity index 81%
rename from tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java
rename to tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FrameworkActionBarWrapper.java
index 6db722e..44c2cd8 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 @@
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 @@
/**
* 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 @@
* ?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 @@
* 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 @@
* 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 @@
@Override
protected void setupActionBar() {
- super.setupActionBar();
// Set the navigation mode.
int navMode = mCallback.getNavigationMode();
@@ -278,16 +270,6 @@
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 @@
}
@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 @@
@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 60f5331..127cb72 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 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 @@
/**
* 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 @@
// 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 @@
* 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 @@
* 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 @@
*/
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 @@
*/
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 @@
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 @@
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 4637bfd..b086355 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.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 @@
// 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) {
@@ -1112,8 +1114,7 @@
}
} else {
// action bar overrides title bar so only look for this one if action bar is hidden
- boolean windowNoTitle = getBooleanThemeValue(resources,
- "windowNoTitle", false, !isThemeAppCompat(resources));
+ boolean windowNoTitle = getBooleanThemeValue(resources, "windowNoTitle", false, true);
if (!windowNoTitle) {
@@ -1190,8 +1191,22 @@
// 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 +1662,13 @@
/**
* 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() {