Merge "Keyguard FP UX update"
diff --git a/api/current.txt b/api/current.txt
index 58130a6..75a8df9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1301,6 +1301,7 @@
field public static final int thicknessRatio = 16843164; // 0x101019c
field public static final int thumb = 16843074; // 0x1010142
field public static final int thumbOffset = 16843075; // 0x1010143
+ field public static final int thumbPosition = 16844013; // 0x10104ed
field public static final int thumbTextPadding = 16843634; // 0x1010372
field public static final int thumbTint = 16843889; // 0x1010471
field public static final int thumbTintMode = 16843890; // 0x1010472
@@ -3975,6 +3976,48 @@
field public java.lang.String serviceDetails;
}
+ public final class AssistAction {
+ method public static void updateAssistData(android.os.Bundle, android.os.Bundle);
+ field public static final java.lang.String ASSIST_ACTION_KEY = "android:assist_action";
+ field public static final java.lang.String KEY_ACTION_OBJECT = "object";
+ field public static final java.lang.String KEY_ACTION_STATUS = "actionStatus";
+ field public static final java.lang.String KEY_DESCRIPTION = "description";
+ field public static final java.lang.String KEY_ID = "@id";
+ field public static final java.lang.String KEY_NAME = "name";
+ field public static final java.lang.String KEY_TYPE = "@type";
+ field public static final java.lang.String KEY_URL = "url";
+ field public static final java.lang.String STATUS_TYPE_ACTIVE = "ActiveActionStatus";
+ field public static final java.lang.String STATUS_TYPE_COMPLETED = "CompletedActionStatus";
+ field public static final java.lang.String TYPE_ADD_ACTION = "AddAction";
+ field public static final java.lang.String TYPE_BOOKMARK_ACTION = "BookmarkAction";
+ field public static final java.lang.String TYPE_LIKE_ACTION = "LikeAction";
+ field public static final java.lang.String TYPE_LISTEN_ACTION = "ListenAction";
+ field public static final java.lang.String TYPE_VIEW_ACTION = "ViewAction";
+ field public static final java.lang.String TYPE_WANT_ACTION = "WantAction";
+ field public static final java.lang.String TYPE_WATCH_ACTION = "WatchAction";
+ }
+
+ public static final class AssistAction.ActionBuilder {
+ ctor public AssistAction.ActionBuilder();
+ method public android.os.Bundle build();
+ method public android.app.AssistAction.ActionBuilder set(java.lang.String, java.lang.String);
+ method public android.app.AssistAction.ActionBuilder set(java.lang.String, android.os.Bundle);
+ method public android.app.AssistAction.ActionBuilder setObject(android.os.Bundle);
+ method public android.app.AssistAction.ActionBuilder setType(java.lang.String);
+ }
+
+ public static final class AssistAction.ThingBuilder {
+ ctor public AssistAction.ThingBuilder();
+ method public android.os.Bundle build();
+ method public android.app.AssistAction.ThingBuilder set(java.lang.String, java.lang.String);
+ method public android.app.AssistAction.ThingBuilder set(java.lang.String, android.os.Bundle);
+ method public android.app.AssistAction.ThingBuilder setDescription(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setId(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setName(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setType(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setUrl(android.net.Uri);
+ }
+
public class AssistContent implements android.os.Parcelable {
ctor public AssistContent();
method public int describeContents();
@@ -5316,6 +5359,11 @@
method public void onRejectSharedElements(java.util.List<android.view.View>);
method public void onSharedElementEnd(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
method public void onSharedElementStart(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
+ method public void onSharedElementsArrived(java.util.List<java.lang.String>, java.util.List<android.view.View>, android.app.SharedElementCallback.OnSharedElementsReadyListener);
+ }
+
+ public static abstract interface SharedElementCallback.OnSharedElementsReadyListener {
+ method public abstract void onSharedElementsReady();
}
public deprecated class TabActivity extends android.app.ActivityGroup {
@@ -9161,6 +9209,7 @@
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand";
field public static final java.lang.String FEATURE_GAMEPAD = "android.hardware.gamepad";
+ field public static final java.lang.String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors";
field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen";
field public static final java.lang.String FEATURE_INPUT_METHODS = "android.software.input_methods";
field public static final java.lang.String FEATURE_LEANBACK = "android.software.leanback";
@@ -9725,15 +9774,13 @@
method public void registerDataSetObserver(android.database.DataSetObserver);
method public boolean requery();
method public android.os.Bundle respond(android.os.Bundle);
+ method public void setExtras(android.os.Bundle);
method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public void unregisterContentObserver(android.database.ContentObserver);
method public void unregisterDataSetObserver(android.database.DataSetObserver);
- field protected boolean mClosed;
- field protected android.content.ContentResolver mContentResolver;
- field protected deprecated java.lang.Long mCurrentRowID;
- field protected int mPos;
- field protected deprecated int mRowIdColumnIndex;
- field protected deprecated java.util.HashMap<java.lang.Long, java.util.Map<java.lang.String, java.lang.Object>> mUpdatedRows;
+ field protected deprecated boolean mClosed;
+ field protected deprecated android.content.ContentResolver mContentResolver;
+ field protected deprecated int mPos;
}
protected static class AbstractCursor.SelfContentObserver extends android.database.ContentObserver {
@@ -9833,6 +9880,7 @@
method public abstract void registerDataSetObserver(android.database.DataSetObserver);
method public abstract deprecated boolean requery();
method public abstract android.os.Bundle respond(android.os.Bundle);
+ method public abstract void setExtras(android.os.Bundle);
method public abstract void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public abstract void unregisterContentObserver(android.database.ContentObserver);
method public abstract void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -9904,7 +9952,7 @@
ctor public CursorWrapper(android.database.Cursor);
method public void close();
method public void copyStringToBuffer(int, android.database.CharArrayBuffer);
- method public void deactivate();
+ method public deprecated void deactivate();
method public byte[] getBlob(int);
method public int getColumnCount();
method public int getColumnIndex(java.lang.String);
@@ -9938,8 +9986,9 @@
method public boolean moveToPrevious();
method public void registerContentObserver(android.database.ContentObserver);
method public void registerDataSetObserver(android.database.DataSetObserver);
- method public boolean requery();
+ method public deprecated boolean requery();
method public android.os.Bundle respond(android.os.Bundle);
+ method public void setExtras(android.os.Bundle);
method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public void unregisterContentObserver(android.database.ContentObserver);
method public void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -13571,6 +13620,8 @@
method public android.util.Range<java.lang.Integer>[] getHighSpeedVideoFpsRangesFor(android.util.Size);
method public android.util.Size[] getHighSpeedVideoSizes();
method public android.util.Size[] getHighSpeedVideoSizesFor(android.util.Range<java.lang.Integer>);
+ method public final int[] getInputFormats();
+ method public android.util.Size[] getInputSizes(int);
method public final int[] getOutputFormats();
method public long getOutputMinFrameDuration(int, android.util.Size);
method public long getOutputMinFrameDuration(java.lang.Class<T>, android.util.Size);
@@ -13578,6 +13629,7 @@
method public android.util.Size[] getOutputSizes(int);
method public long getOutputStallDuration(int, android.util.Size);
method public long getOutputStallDuration(java.lang.Class<T>, android.util.Size);
+ method public final int[] getValidOutputFormatsForInput(int);
method public boolean isOutputSupportedFor(int);
method public static boolean isOutputSupportedFor(java.lang.Class<T>);
method public boolean isOutputSupportedFor(android.view.Surface);
@@ -14989,6 +15041,7 @@
field public static final java.lang.String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
field public static final java.lang.String PARAMETER_KEY_SUSPEND = "drop-input-frames";
field public static final java.lang.String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate";
+ field public static final int REASON_RECLAIMED = 1; // 0x1
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
}
@@ -15004,6 +15057,7 @@
public static abstract class MediaCodec.Callback {
ctor public MediaCodec.Callback();
+ method public void onCodecReleased(android.media.MediaCodec, int);
method public abstract void onError(android.media.MediaCodec, android.media.MediaCodec.CodecException);
method public abstract void onInputBufferAvailable(android.media.MediaCodec, int);
method public abstract void onOutputBufferAvailable(android.media.MediaCodec, int, android.media.MediaCodec.BufferInfo);
@@ -15269,6 +15323,7 @@
method public static final boolean isCryptoSchemeSupported(java.util.UUID);
method public final void release();
method public final boolean requiresSecureDecoderComponent(java.lang.String);
+ method public final void setMediaDrmSession(byte[]) throws android.media.MediaCryptoException;
}
public final class MediaCryptoException extends java.lang.Exception {
@@ -15324,6 +15379,8 @@
method public void removeKeys(byte[]);
method public void restoreKeys(byte[], byte[]);
method public void setOnEventListener(android.media.MediaDrm.OnEventListener);
+ method public void setOnExpirationUpdateListener(android.media.MediaDrm.OnExpirationUpdateListener, android.os.Handler);
+ method public void setOnKeysChangeListener(android.media.MediaDrm.OnKeysChangeListener, android.os.Handler);
method public void setPropertyByteArray(java.lang.String, byte[]);
method public void setPropertyString(java.lang.String, java.lang.String);
field public static final int EVENT_KEY_EXPIRED = 3; // 0x3
@@ -15331,6 +15388,11 @@
field public static final deprecated int EVENT_PROVISION_REQUIRED = 1; // 0x1
field public static final int EVENT_SESSION_RECLAIMED = 5; // 0x5
field public static final int EVENT_VENDOR_DEFINED = 4; // 0x4
+ field public static final int KEY_STATUS_EXPIRED = 1; // 0x1
+ field public static final int KEY_STATUS_INTERNAL_ERROR = 4; // 0x4
+ field public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2; // 0x2
+ field public static final int KEY_STATUS_PENDING = 3; // 0x3
+ field public static final int KEY_STATUS_USABLE = 0; // 0x0
field public static final int KEY_TYPE_OFFLINE = 2; // 0x2
field public static final int KEY_TYPE_RELEASE = 3; // 0x3
field public static final int KEY_TYPE_STREAMING = 1; // 0x1
@@ -15357,6 +15419,11 @@
method public int getRequestType();
}
+ public static final class MediaDrm.KeyStatus {
+ method public byte[] getKeyId();
+ method public int getStatusCode();
+ }
+
public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
method public java.lang.String getDiagnosticInfo();
}
@@ -15365,6 +15432,14 @@
method public abstract void onEvent(android.media.MediaDrm, byte[], int, int, byte[]);
}
+ public static abstract interface MediaDrm.OnExpirationUpdateListener {
+ method public abstract void onExpirationUpdate(android.media.MediaDrm, byte[], long);
+ }
+
+ public static abstract interface MediaDrm.OnKeysChangeListener {
+ method public abstract void onKeysChange(android.media.MediaDrm, byte[], java.util.List<android.media.MediaDrm.KeyStatus>, boolean);
+ }
+
public static final class MediaDrm.ProvisionRequest {
method public byte[] getData();
method public java.lang.String getDefaultUrl();
@@ -15451,6 +15526,7 @@
field public static final java.lang.String KEY_MAX_INPUT_SIZE = "max-input-size";
field public static final java.lang.String KEY_MAX_WIDTH = "max-width";
field public static final java.lang.String KEY_MIME = "mime";
+ field public static final java.lang.String KEY_OPERATING_RATE = "operating-rate";
field public static final java.lang.String KEY_PRIORITY = "priority";
field public static final java.lang.String KEY_PROFILE = "profile";
field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown";
@@ -15573,6 +15649,7 @@
field public static final int METADATA_KEY_ARTIST = 2; // 0x2
field public static final int METADATA_KEY_AUTHOR = 3; // 0x3
field public static final int METADATA_KEY_BITRATE = 20; // 0x14
+ field public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25; // 0x19
field public static final int METADATA_KEY_CD_TRACK_NUMBER = 0; // 0x0
field public static final int METADATA_KEY_COMPILATION = 15; // 0xf
field public static final int METADATA_KEY_COMPOSER = 4; // 0x4
@@ -16758,9 +16835,8 @@
method public int describeContents();
method public int getId();
method public int getInputPortCount();
- method public android.media.midi.MidiDeviceInfo.PortInfo getInputPortInfo(int);
method public int getOutputPortCount();
- method public android.media.midi.MidiDeviceInfo.PortInfo getOutputPortInfo(int);
+ method public android.media.midi.MidiDeviceInfo.PortInfo[] getPortList();
method public android.os.Bundle getProperties();
method public int getType();
method public boolean isPrivate();
@@ -16812,11 +16888,17 @@
public final class MidiManager {
method public android.media.midi.MidiDeviceInfo[] getDeviceList();
+ method public void openBluetoothDevice(android.bluetooth.BluetoothDevice, android.media.midi.MidiManager.BluetoothOpenCallback, android.os.Handler);
method public void openDevice(android.media.midi.MidiDeviceInfo, android.media.midi.MidiManager.DeviceOpenCallback, android.os.Handler);
method public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler);
method public void unregisterDeviceCallback(android.media.midi.MidiManager.DeviceCallback);
}
+ public static abstract class MidiManager.BluetoothOpenCallback {
+ ctor public MidiManager.BluetoothOpenCallback();
+ method public abstract void onDeviceOpened(android.bluetooth.BluetoothDevice, android.media.midi.MidiDevice);
+ }
+
public static class MidiManager.DeviceCallback {
ctor public MidiManager.DeviceCallback();
method public void onDeviceAdded(android.media.midi.MidiDeviceInfo);
@@ -16838,6 +16920,7 @@
public abstract class MidiReceiver {
ctor public MidiReceiver();
+ method public void flush() throws java.io.IOException;
method public int getMaxMessageSize();
method public abstract void onReceive(byte[], int, int, long) throws java.io.IOException;
method public void send(byte[], int, int) throws java.io.IOException;
@@ -17524,6 +17607,7 @@
field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2
field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0
field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; // 0x1
+ field public static final int VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = 4; // 0x4
field public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3; // 0x3
field public static final int VIDEO_UNAVAILABLE_REASON_TUNING = 1; // 0x1
field public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0; // 0x0
@@ -22242,6 +22326,9 @@
public class BatteryManager {
method public int getIntProperty(int);
method public long getLongProperty(int);
+ method public boolean isCharging();
+ field public static final java.lang.String ACTION_CHARGING = "android.os.action.CHARGING";
+ field public static final java.lang.String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
field public static final int BATTERY_HEALTH_COLD = 7; // 0x7
field public static final int BATTERY_HEALTH_DEAD = 4; // 0x4
field public static final int BATTERY_HEALTH_GOOD = 2; // 0x2
@@ -26825,7 +26912,6 @@
public static final class VoicemailContract.Status implements android.provider.BaseColumns {
method public static android.net.Uri buildSourceUri(java.lang.String);
- method public static void setStatus(android.content.Context, android.telecom.PhoneAccountHandle, int, int, int);
field public static final java.lang.String CONFIGURATION_STATE = "configuration_state";
field public static final int CONFIGURATION_STATE_CAN_BE_CONFIGURED = 2; // 0x2
field public static final int CONFIGURATION_STATE_NOT_CONFIGURED = 1; // 0x1
@@ -26849,9 +26935,6 @@
public static final class VoicemailContract.Voicemails implements android.provider.BaseColumns android.provider.OpenableColumns {
method public static android.net.Uri buildSourceUri(java.lang.String);
- method public static int deleteAll(android.content.Context);
- method public static android.net.Uri insert(android.content.Context, android.telecom.Voicemail);
- method public static int insert(android.content.Context, java.util.List<android.telecom.Voicemail>);
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATE = "date";
field public static final java.lang.String DELETED = "deleted";
@@ -29310,22 +29393,6 @@
package android.telecom {
- public class AuthenticatorService extends android.app.Service {
- ctor public AuthenticatorService();
- method public android.os.IBinder onBind(android.content.Intent);
- }
-
- public class AuthenticatorService.Authenticator extends android.accounts.AbstractAccountAuthenticator {
- ctor public AuthenticatorService.Authenticator(android.content.Context);
- method public android.os.Bundle addAccount(android.accounts.AccountAuthenticatorResponse, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle) throws android.accounts.NetworkErrorException;
- method public android.os.Bundle confirmCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, android.os.Bundle) throws android.accounts.NetworkErrorException;
- method public android.os.Bundle editProperties(android.accounts.AccountAuthenticatorResponse, java.lang.String);
- method public android.os.Bundle getAuthToken(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
- method public java.lang.String getAuthTokenLabel(java.lang.String);
- method public android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException;
- method public android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
- }
-
public class PhoneAccount implements android.os.Parcelable {
method public static android.telecom.PhoneAccount.Builder builder(android.telecom.PhoneAccountHandle, java.lang.CharSequence);
method public android.graphics.drawable.Drawable createIconDrawable(android.content.Context);
@@ -29437,36 +29504,6 @@
field public static final int TX_ENABLED = 1; // 0x1
}
- public class Voicemail implements android.os.Parcelable {
- method public static android.telecom.Voicemail.Builder createForInsertion(long, java.lang.String);
- method public static android.telecom.Voicemail.Builder createForUpdate(long, java.lang.String);
- method public int describeContents();
- method public long getDuration();
- method public long getId();
- method public java.lang.String getNumber();
- method public java.lang.String getSourceData();
- method public java.lang.String getSourcePackage();
- method public long getTimestampMillis();
- method public android.net.Uri getUri();
- method public boolean hasContent();
- method public boolean isRead();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.telecom.Voicemail> CREATOR;
- }
-
- public static class Voicemail.Builder {
- method public android.telecom.Voicemail build();
- method public android.telecom.Voicemail.Builder setDuration(long);
- method public android.telecom.Voicemail.Builder setHasContent(boolean);
- method public android.telecom.Voicemail.Builder setId(long);
- method public android.telecom.Voicemail.Builder setIsRead(boolean);
- method public android.telecom.Voicemail.Builder setNumber(java.lang.String);
- method public android.telecom.Voicemail.Builder setSourceData(java.lang.String);
- method public android.telecom.Voicemail.Builder setSourcePackage(java.lang.String);
- method public android.telecom.Voicemail.Builder setTimestamp(long);
- method public android.telecom.Voicemail.Builder setUri(android.net.Uri);
- }
-
}
package android.telephony {
@@ -30570,7 +30607,7 @@
ctor public MockCursor();
method public void close();
method public void copyStringToBuffer(int, android.database.CharArrayBuffer);
- method public void deactivate();
+ method public deprecated void deactivate();
method public byte[] getBlob(int);
method public int getColumnCount();
method public int getColumnIndex(java.lang.String);
@@ -30603,8 +30640,9 @@
method public boolean moveToPrevious();
method public void registerContentObserver(android.database.ContentObserver);
method public void registerDataSetObserver(android.database.DataSetObserver);
- method public boolean requery();
+ method public deprecated boolean requery();
method public android.os.Bundle respond(android.os.Bundle);
+ method public void setExtras(android.os.Bundle);
method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public void unregisterContentObserver(android.database.ContentObserver);
method public void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -32552,6 +32590,7 @@
ctor public TransitionManager();
method public static void beginDelayedTransition(android.view.ViewGroup);
method public static void beginDelayedTransition(android.view.ViewGroup, android.transition.Transition);
+ method public static void endTransitions(android.view.ViewGroup);
method public static void go(android.transition.Scene);
method public static void go(android.transition.Scene, android.transition.Transition);
method public void setTransition(android.transition.Scene, android.transition.Transition);
@@ -33570,10 +33609,10 @@
method public java.lang.String getName();
method public int getProductId();
method public int getSources();
- method public java.lang.String getUniqueId();
method public int getVendorId();
method public android.os.Vibrator getVibrator();
method public boolean[] hasKeys(int...);
+ method public boolean hasMic();
method public boolean isVirtual();
method public boolean supportsSource(int);
method public void writeToParcel(android.os.Parcel, int);
diff --git a/api/removed.txt b/api/removed.txt
index c2b9d3e..0c433c3 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -6,6 +6,16 @@
}
+package android.database {
+
+ public abstract class AbstractCursor implements android.database.CrossProcessCursor {
+ field protected java.lang.Long mCurrentRowID;
+ field protected int mRowIdColumnIndex;
+ field protected java.util.HashMap<java.lang.Long, java.util.Map<java.lang.String, java.lang.Object>> mUpdatedRows;
+ }
+
+}
+
package android.media {
public class AudioFormat {
diff --git a/api/system-current.txt b/api/system-current.txt
index 4fcf9f0..d272c20 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1378,6 +1378,7 @@
field public static final int thicknessRatio = 16843164; // 0x101019c
field public static final int thumb = 16843074; // 0x1010142
field public static final int thumbOffset = 16843075; // 0x1010143
+ field public static final int thumbPosition = 16844013; // 0x10104ed
field public static final int thumbTextPadding = 16843634; // 0x1010372
field public static final int thumbTint = 16843889; // 0x1010471
field public static final int thumbTintMode = 16843890; // 0x1010472
@@ -4066,6 +4067,48 @@
field public java.lang.String serviceDetails;
}
+ public final class AssistAction {
+ method public static void updateAssistData(android.os.Bundle, android.os.Bundle);
+ field public static final java.lang.String ASSIST_ACTION_KEY = "android:assist_action";
+ field public static final java.lang.String KEY_ACTION_OBJECT = "object";
+ field public static final java.lang.String KEY_ACTION_STATUS = "actionStatus";
+ field public static final java.lang.String KEY_DESCRIPTION = "description";
+ field public static final java.lang.String KEY_ID = "@id";
+ field public static final java.lang.String KEY_NAME = "name";
+ field public static final java.lang.String KEY_TYPE = "@type";
+ field public static final java.lang.String KEY_URL = "url";
+ field public static final java.lang.String STATUS_TYPE_ACTIVE = "ActiveActionStatus";
+ field public static final java.lang.String STATUS_TYPE_COMPLETED = "CompletedActionStatus";
+ field public static final java.lang.String TYPE_ADD_ACTION = "AddAction";
+ field public static final java.lang.String TYPE_BOOKMARK_ACTION = "BookmarkAction";
+ field public static final java.lang.String TYPE_LIKE_ACTION = "LikeAction";
+ field public static final java.lang.String TYPE_LISTEN_ACTION = "ListenAction";
+ field public static final java.lang.String TYPE_VIEW_ACTION = "ViewAction";
+ field public static final java.lang.String TYPE_WANT_ACTION = "WantAction";
+ field public static final java.lang.String TYPE_WATCH_ACTION = "WatchAction";
+ }
+
+ public static final class AssistAction.ActionBuilder {
+ ctor public AssistAction.ActionBuilder();
+ method public android.os.Bundle build();
+ method public android.app.AssistAction.ActionBuilder set(java.lang.String, java.lang.String);
+ method public android.app.AssistAction.ActionBuilder set(java.lang.String, android.os.Bundle);
+ method public android.app.AssistAction.ActionBuilder setObject(android.os.Bundle);
+ method public android.app.AssistAction.ActionBuilder setType(java.lang.String);
+ }
+
+ public static final class AssistAction.ThingBuilder {
+ ctor public AssistAction.ThingBuilder();
+ method public android.os.Bundle build();
+ method public android.app.AssistAction.ThingBuilder set(java.lang.String, java.lang.String);
+ method public android.app.AssistAction.ThingBuilder set(java.lang.String, android.os.Bundle);
+ method public android.app.AssistAction.ThingBuilder setDescription(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setId(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setName(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setType(java.lang.String);
+ method public android.app.AssistAction.ThingBuilder setUrl(android.net.Uri);
+ }
+
public class AssistContent implements android.os.Parcelable {
ctor public AssistContent();
method public int describeContents();
@@ -5407,6 +5450,11 @@
method public void onRejectSharedElements(java.util.List<android.view.View>);
method public void onSharedElementEnd(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
method public void onSharedElementStart(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
+ method public void onSharedElementsArrived(java.util.List<java.lang.String>, java.util.List<android.view.View>, android.app.SharedElementCallback.OnSharedElementsReadyListener);
+ }
+
+ public static abstract interface SharedElementCallback.OnSharedElementsReadyListener {
+ method public abstract void onSharedElementsReady();
}
public deprecated class TabActivity extends android.app.ActivityGroup {
@@ -5914,6 +5962,7 @@
method public android.app.backup.RestoreSession beginRestoreSession();
method public void dataChanged();
method public static void dataChanged(java.lang.String);
+ method public long getAvailableRestoreToken(java.lang.String);
method public java.lang.String getCurrentTransport();
method public boolean isBackupEnabled();
method public java.lang.String[] listAllTransports();
@@ -9416,6 +9465,7 @@
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand";
field public static final java.lang.String FEATURE_GAMEPAD = "android.hardware.gamepad";
+ field public static final java.lang.String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors";
field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen";
field public static final java.lang.String FEATURE_INPUT_METHODS = "android.software.input_methods";
field public static final java.lang.String FEATURE_LEANBACK = "android.software.leanback";
@@ -10015,15 +10065,13 @@
method public void registerDataSetObserver(android.database.DataSetObserver);
method public boolean requery();
method public android.os.Bundle respond(android.os.Bundle);
+ method public void setExtras(android.os.Bundle);
method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public void unregisterContentObserver(android.database.ContentObserver);
method public void unregisterDataSetObserver(android.database.DataSetObserver);
- field protected boolean mClosed;
- field protected android.content.ContentResolver mContentResolver;
- field protected deprecated java.lang.Long mCurrentRowID;
- field protected int mPos;
- field protected deprecated int mRowIdColumnIndex;
- field protected deprecated java.util.HashMap<java.lang.Long, java.util.Map<java.lang.String, java.lang.Object>> mUpdatedRows;
+ field protected deprecated boolean mClosed;
+ field protected deprecated android.content.ContentResolver mContentResolver;
+ field protected deprecated int mPos;
}
protected static class AbstractCursor.SelfContentObserver extends android.database.ContentObserver {
@@ -10123,6 +10171,7 @@
method public abstract void registerDataSetObserver(android.database.DataSetObserver);
method public abstract deprecated boolean requery();
method public abstract android.os.Bundle respond(android.os.Bundle);
+ method public abstract void setExtras(android.os.Bundle);
method public abstract void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public abstract void unregisterContentObserver(android.database.ContentObserver);
method public abstract void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -10194,7 +10243,7 @@
ctor public CursorWrapper(android.database.Cursor);
method public void close();
method public void copyStringToBuffer(int, android.database.CharArrayBuffer);
- method public void deactivate();
+ method public deprecated void deactivate();
method public byte[] getBlob(int);
method public int getColumnCount();
method public int getColumnIndex(java.lang.String);
@@ -10228,8 +10277,9 @@
method public boolean moveToPrevious();
method public void registerContentObserver(android.database.ContentObserver);
method public void registerDataSetObserver(android.database.DataSetObserver);
- method public boolean requery();
+ method public deprecated boolean requery();
method public android.os.Bundle respond(android.os.Bundle);
+ method public void setExtras(android.os.Bundle);
method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public void unregisterContentObserver(android.database.ContentObserver);
method public void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -13863,6 +13913,8 @@
method public android.util.Range<java.lang.Integer>[] getHighSpeedVideoFpsRangesFor(android.util.Size);
method public android.util.Size[] getHighSpeedVideoSizes();
method public android.util.Size[] getHighSpeedVideoSizesFor(android.util.Range<java.lang.Integer>);
+ method public final int[] getInputFormats();
+ method public android.util.Size[] getInputSizes(int);
method public final int[] getOutputFormats();
method public long getOutputMinFrameDuration(int, android.util.Size);
method public long getOutputMinFrameDuration(java.lang.Class<T>, android.util.Size);
@@ -13870,6 +13922,7 @@
method public android.util.Size[] getOutputSizes(int);
method public long getOutputStallDuration(int, android.util.Size);
method public long getOutputStallDuration(java.lang.Class<T>, android.util.Size);
+ method public final int[] getValidOutputFormatsForInput(int);
method public boolean isOutputSupportedFor(int);
method public static boolean isOutputSupportedFor(java.lang.Class<T>);
method public boolean isOutputSupportedFor(android.view.Surface);
@@ -16197,6 +16250,7 @@
field public static final java.lang.String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
field public static final java.lang.String PARAMETER_KEY_SUSPEND = "drop-input-frames";
field public static final java.lang.String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate";
+ field public static final int REASON_RECLAIMED = 1; // 0x1
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
}
@@ -16212,6 +16266,7 @@
public static abstract class MediaCodec.Callback {
ctor public MediaCodec.Callback();
+ method public void onCodecReleased(android.media.MediaCodec, int);
method public abstract void onError(android.media.MediaCodec, android.media.MediaCodec.CodecException);
method public abstract void onInputBufferAvailable(android.media.MediaCodec, int);
method public abstract void onOutputBufferAvailable(android.media.MediaCodec, int, android.media.MediaCodec.BufferInfo);
@@ -16477,6 +16532,7 @@
method public static final boolean isCryptoSchemeSupported(java.util.UUID);
method public final void release();
method public final boolean requiresSecureDecoderComponent(java.lang.String);
+ method public final void setMediaDrmSession(byte[]) throws android.media.MediaCryptoException;
}
public final class MediaCryptoException extends java.lang.Exception {
@@ -16532,6 +16588,8 @@
method public void removeKeys(byte[]);
method public void restoreKeys(byte[], byte[]);
method public void setOnEventListener(android.media.MediaDrm.OnEventListener);
+ method public void setOnExpirationUpdateListener(android.media.MediaDrm.OnExpirationUpdateListener, android.os.Handler);
+ method public void setOnKeysChangeListener(android.media.MediaDrm.OnKeysChangeListener, android.os.Handler);
method public void setPropertyByteArray(java.lang.String, byte[]);
method public void setPropertyString(java.lang.String, java.lang.String);
method public void unprovisionDevice();
@@ -16540,6 +16598,11 @@
field public static final deprecated int EVENT_PROVISION_REQUIRED = 1; // 0x1
field public static final int EVENT_SESSION_RECLAIMED = 5; // 0x5
field public static final int EVENT_VENDOR_DEFINED = 4; // 0x4
+ field public static final int KEY_STATUS_EXPIRED = 1; // 0x1
+ field public static final int KEY_STATUS_INTERNAL_ERROR = 4; // 0x4
+ field public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2; // 0x2
+ field public static final int KEY_STATUS_PENDING = 3; // 0x3
+ field public static final int KEY_STATUS_USABLE = 0; // 0x0
field public static final int KEY_TYPE_OFFLINE = 2; // 0x2
field public static final int KEY_TYPE_RELEASE = 3; // 0x3
field public static final int KEY_TYPE_STREAMING = 1; // 0x1
@@ -16566,6 +16629,11 @@
method public int getRequestType();
}
+ public static final class MediaDrm.KeyStatus {
+ method public byte[] getKeyId();
+ method public int getStatusCode();
+ }
+
public static final class MediaDrm.MediaDrmStateException extends java.lang.IllegalStateException {
method public java.lang.String getDiagnosticInfo();
}
@@ -16574,6 +16642,14 @@
method public abstract void onEvent(android.media.MediaDrm, byte[], int, int, byte[]);
}
+ public static abstract interface MediaDrm.OnExpirationUpdateListener {
+ method public abstract void onExpirationUpdate(android.media.MediaDrm, byte[], long);
+ }
+
+ public static abstract interface MediaDrm.OnKeysChangeListener {
+ method public abstract void onKeysChange(android.media.MediaDrm, byte[], java.util.List<android.media.MediaDrm.KeyStatus>, boolean);
+ }
+
public static final class MediaDrm.ProvisionRequest {
method public byte[] getData();
method public java.lang.String getDefaultUrl();
@@ -16660,6 +16736,7 @@
field public static final java.lang.String KEY_MAX_INPUT_SIZE = "max-input-size";
field public static final java.lang.String KEY_MAX_WIDTH = "max-width";
field public static final java.lang.String KEY_MIME = "mime";
+ field public static final java.lang.String KEY_OPERATING_RATE = "operating-rate";
field public static final java.lang.String KEY_PRIORITY = "priority";
field public static final java.lang.String KEY_PROFILE = "profile";
field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown";
@@ -16782,6 +16859,7 @@
field public static final int METADATA_KEY_ARTIST = 2; // 0x2
field public static final int METADATA_KEY_AUTHOR = 3; // 0x3
field public static final int METADATA_KEY_BITRATE = 20; // 0x14
+ field public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25; // 0x19
field public static final int METADATA_KEY_CD_TRACK_NUMBER = 0; // 0x0
field public static final int METADATA_KEY_COMPILATION = 15; // 0xf
field public static final int METADATA_KEY_COMPOSER = 4; // 0x4
@@ -18033,9 +18111,8 @@
method public int describeContents();
method public int getId();
method public int getInputPortCount();
- method public android.media.midi.MidiDeviceInfo.PortInfo getInputPortInfo(int);
method public int getOutputPortCount();
- method public android.media.midi.MidiDeviceInfo.PortInfo getOutputPortInfo(int);
+ method public android.media.midi.MidiDeviceInfo.PortInfo[] getPortList();
method public android.os.Bundle getProperties();
method public int getType();
method public boolean isPrivate();
@@ -18087,11 +18164,17 @@
public final class MidiManager {
method public android.media.midi.MidiDeviceInfo[] getDeviceList();
+ method public void openBluetoothDevice(android.bluetooth.BluetoothDevice, android.media.midi.MidiManager.BluetoothOpenCallback, android.os.Handler);
method public void openDevice(android.media.midi.MidiDeviceInfo, android.media.midi.MidiManager.DeviceOpenCallback, android.os.Handler);
method public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler);
method public void unregisterDeviceCallback(android.media.midi.MidiManager.DeviceCallback);
}
+ public static abstract class MidiManager.BluetoothOpenCallback {
+ ctor public MidiManager.BluetoothOpenCallback();
+ method public abstract void onDeviceOpened(android.bluetooth.BluetoothDevice, android.media.midi.MidiDevice);
+ }
+
public static class MidiManager.DeviceCallback {
ctor public MidiManager.DeviceCallback();
method public void onDeviceAdded(android.media.midi.MidiDeviceInfo);
@@ -18113,6 +18196,7 @@
public abstract class MidiReceiver {
ctor public MidiReceiver();
+ method public void flush() throws java.io.IOException;
method public int getMaxMessageSize();
method public abstract void onReceive(byte[], int, int, long) throws java.io.IOException;
method public void send(byte[], int, int) throws java.io.IOException;
@@ -18887,6 +18971,7 @@
field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2
field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0
field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; // 0x1
+ field public static final int VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = 4; // 0x4
field public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3; // 0x3
field public static final int VIDEO_UNAVAILABLE_REASON_TUNING = 1; // 0x1
field public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = 0; // 0x0
@@ -24113,6 +24198,9 @@
public class BatteryManager {
method public int getIntProperty(int);
method public long getLongProperty(int);
+ method public boolean isCharging();
+ field public static final java.lang.String ACTION_CHARGING = "android.os.action.CHARGING";
+ field public static final java.lang.String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
field public static final int BATTERY_HEALTH_COLD = 7; // 0x7
field public static final int BATTERY_HEALTH_DEAD = 4; // 0x4
field public static final int BATTERY_HEALTH_GOOD = 2; // 0x2
@@ -30228,6 +30316,7 @@
method public abstract byte[] read() throws android.os.RemoteException;
method public abstract void setOemUnlockEnabled(boolean) throws android.os.RemoteException;
method public abstract void wipe() throws android.os.RemoteException;
+ method public abstract void wipeIfAllowed(android.os.Bundle, android.app.PendingIntent) throws android.os.RemoteException;
method public abstract int write(byte[]) throws android.os.RemoteException;
}
@@ -30239,7 +30328,14 @@
method public byte[] read();
method public void setOemUnlockEnabled(boolean);
method public void wipe();
+ method public void wipeIfAllowed(android.os.Bundle, android.app.PendingIntent);
method public int write(byte[]);
+ field public static final java.lang.String ACTION_WIPE_IF_ALLOWED = "android.service.persistentdata.action.WIPE_IF_ALLOWED";
+ field public static final java.lang.String EXTRA_WIPE_IF_ALLOWED_CALLBACK = "android.service.persistentdata.extra.WIPE_IF_ALLOWED_CALLBACK";
+ field public static final int STATUS_ERROR_NETWORK_ERROR = 2; // 0x2
+ field public static final int STATUS_ERROR_NOT_COMPLIANT = 3; // 0x3
+ field public static final int STATUS_ERROR_REMOTE_EXCEPTION = 1; // 0x1
+ field public static final int STATUS_SUCCESS = 0; // 0x0
}
}
@@ -33122,7 +33218,7 @@
ctor public MockCursor();
method public void close();
method public void copyStringToBuffer(int, android.database.CharArrayBuffer);
- method public void deactivate();
+ method public deprecated void deactivate();
method public byte[] getBlob(int);
method public int getColumnCount();
method public int getColumnIndex(java.lang.String);
@@ -33155,8 +33251,9 @@
method public boolean moveToPrevious();
method public void registerContentObserver(android.database.ContentObserver);
method public void registerDataSetObserver(android.database.DataSetObserver);
- method public boolean requery();
+ method public deprecated boolean requery();
method public android.os.Bundle respond(android.os.Bundle);
+ method public void setExtras(android.os.Bundle);
method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
method public void unregisterContentObserver(android.database.ContentObserver);
method public void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -35106,6 +35203,7 @@
ctor public TransitionManager();
method public static void beginDelayedTransition(android.view.ViewGroup);
method public static void beginDelayedTransition(android.view.ViewGroup, android.transition.Transition);
+ method public static void endTransitions(android.view.ViewGroup);
method public static void go(android.transition.Scene);
method public static void go(android.transition.Scene, android.transition.Transition);
method public void setTransition(android.transition.Scene, android.transition.Transition);
@@ -36124,10 +36222,10 @@
method public java.lang.String getName();
method public int getProductId();
method public int getSources();
- method public java.lang.String getUniqueId();
method public int getVendorId();
method public android.os.Vibrator getVibrator();
method public boolean[] hasKeys(int...);
+ method public boolean hasMic();
method public boolean isVirtual();
method public boolean supportsSource(int);
method public void writeToParcel(android.os.Parcel, int);
diff --git a/api/system-removed.txt b/api/system-removed.txt
index c2b9d3e..0c433c3 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -6,6 +6,16 @@
}
+package android.database {
+
+ public abstract class AbstractCursor implements android.database.CrossProcessCursor {
+ field protected java.lang.Long mCurrentRowID;
+ field protected int mRowIdColumnIndex;
+ field protected java.util.HashMap<java.lang.Long, java.util.Map<java.lang.String, java.lang.Object>> mUpdatedRows;
+ }
+
+}
+
package android.media {
public class AudioFormat {
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 81a01ee..4a9ba3b 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -68,6 +68,7 @@
private static final int VALUE_TYPE_INT = 1;
private static final int VALUE_TYPE_PATH = 2;
private static final int VALUE_TYPE_COLOR = 3;
+ private static final int VALUE_TYPE_UNDEFINED = 4;
private static final boolean DBG_ANIMATOR_INFLATER = false;
@@ -299,8 +300,6 @@
private static PropertyValuesHolder getPVH(TypedArray styledAttributes, int valueType,
int valueFromId, int valueToId, String propertyName) {
- boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
-
TypedValue tvFrom = styledAttributes.peekValue(valueFromId);
boolean hasFrom = (tvFrom != null);
int fromType = hasFrom ? tvFrom.type : 0;
@@ -308,6 +307,17 @@
boolean hasTo = (tvTo != null);
int toType = hasTo ? tvTo.type : 0;
+ if (valueType == VALUE_TYPE_UNDEFINED) {
+ // Check whether it's color type. If not, fall back to default type (i.e. float type)
+ if ((hasFrom && isColorType(fromType)) || (hasTo && isColorType(toType))) {
+ valueType = VALUE_TYPE_COLOR;
+ } else {
+ valueType = VALUE_TYPE_FLOAT;
+ }
+ }
+
+ boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
+
PropertyValuesHolder returnValue = null;
if (valueType == VALUE_TYPE_PATH) {
@@ -341,12 +351,8 @@
} else {
TypeEvaluator evaluator = null;
// Integer and float value types are handled here.
- if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) ||
- (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
+ if (valueType == VALUE_TYPE_COLOR) {
// special case for colors: ignore valueType and get ints
- getFloats = false;
evaluator = ArgbEvaluator.getInstance();
}
if (getFloats) {
@@ -383,8 +389,7 @@
if (hasFrom) {
if (fromType == TypedValue.TYPE_DIMENSION) {
valueFrom = (int) styledAttributes.getDimension(valueFromId, 0f);
- } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ } else if (isColorType(fromType)) {
valueFrom = styledAttributes.getColor(valueFromId, 0);
} else {
valueFrom = styledAttributes.getInt(valueFromId, 0);
@@ -392,8 +397,7 @@
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
- } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ } else if (isColorType(toType)) {
valueTo = styledAttributes.getColor(valueToId, 0);
} else {
valueTo = styledAttributes.getInt(valueToId, 0);
@@ -406,8 +410,7 @@
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
- } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ } else if (isColorType(toType)) {
valueTo = styledAttributes.getColor(valueToId, 0);
} else {
valueTo = styledAttributes.getInt(valueToId, 0);
@@ -613,8 +616,7 @@
if (hasFrom) {
if (fromType == TypedValue.TYPE_DIMENSION) {
valueFrom = (int) arrayAnimator.getDimension(valueFromIndex, 0f);
- } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ } else if (isColorType(fromType)) {
valueFrom = arrayAnimator.getColor(valueFromIndex, 0);
} else {
valueFrom = arrayAnimator.getInt(valueFromIndex, 0);
@@ -622,8 +624,7 @@
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
- } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ } else if (isColorType(toType)) {
valueTo = arrayAnimator.getColor(valueToIndex, 0);
} else {
valueTo = arrayAnimator.getInt(valueToIndex, 0);
@@ -636,8 +637,7 @@
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
- } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
- (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ } else if (isColorType(toType)) {
valueTo = arrayAnimator.getColor(valueToIndex, 0);
} else {
valueTo = arrayAnimator.getInt(valueToIndex, 0);
@@ -749,7 +749,8 @@
}
String propertyName = a.getString(R.styleable.PropertyValuesHolder_propertyName);
int valueType = a.getInt(R.styleable.PropertyValuesHolder_valueType,
- VALUE_TYPE_FLOAT);
+ VALUE_TYPE_UNDEFINED);
+
PropertyValuesHolder pvh = loadPvh(res, theme, parser, propertyName, valueType);
if (pvh == null) {
pvh = getPVH(a, valueType,
@@ -793,6 +794,7 @@
}
}
+ // Load property values holder if there are keyframes defined in it. Otherwise return null.
private static PropertyValuesHolder loadPvh(Resources res, Theme theme, XmlPullParser parser,
String propertyName, int valueType)
throws XmlPullParserException, IOException {
@@ -928,7 +930,17 @@
float fraction = a.getFloat(R.styleable.Keyframe_fraction, -1);
- boolean hasValue = a.peekValue(R.styleable.Keyframe_value) != null;
+ TypedValue keyframeValue = a.peekValue(R.styleable.Keyframe_value);
+ boolean hasValue = (keyframeValue != null);
+ if (valueType == VALUE_TYPE_UNDEFINED) {
+ // When no value type is provided, check whether it's a color type first.
+ // If not, fall back to default value type (i.e. float type).
+ if (hasValue && isColorType(keyframeValue.type)) {
+ valueType = VALUE_TYPE_COLOR;
+ } else {
+ valueType = VALUE_TYPE_FLOAT;
+ }
+ }
if (hasValue) {
switch (valueType) {
@@ -1028,4 +1040,8 @@
return sTmpTypedValue.changingConfigurations;
}
}
+
+ private static boolean isColorType(int type) {
+ return (type >= TypedValue.TYPE_FIRST_COLOR_INT) && (type <= TypedValue.TYPE_LAST_COLOR_INT);
+ }
}
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 2386007..6ffa5dd 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1478,6 +1478,12 @@
anim.mInitialized = false;
anim.mPlayingState = STOPPED;
anim.mStartedDelay = false;
+ anim.mStarted = false;
+ anim.mRunning = false;
+ anim.mPaused = false;
+ anim.mResumed = false;
+ anim.mStartListenersCalled = false;
+
PropertyValuesHolder[] oldValues = mValues;
if (oldValues != null) {
int numValues = oldValues.length;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 8f125d7..2b35cd4 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -269,45 +269,51 @@
* all activities that are visible to the user. */
public static final int PROCESS_STATE_TOP = 2;
+ /** @hide Process is hosting a foreground service. */
+ public static final int PROCESS_STATE_FOREGROUND_SERVICE = 3;
+
+ /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
+ public static final int PROCESS_STATE_TOP_SLEEPING = 4;
+
/** @hide Process is important to the user, and something they are aware of. */
- public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 3;
+ public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
/** @hide Process is important to the user, but not something they are aware of. */
- public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 4;
+ public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 6;
/** @hide Process is in the background running a backup/restore operation. */
- public static final int PROCESS_STATE_BACKUP = 5;
+ public static final int PROCESS_STATE_BACKUP = 7;
/** @hide Process is in the background, but it can't restore its state so we want
* to try to avoid killing it. */
- public static final int PROCESS_STATE_HEAVY_WEIGHT = 6;
+ public static final int PROCESS_STATE_HEAVY_WEIGHT = 8;
/** @hide Process is in the background running a service. Unlike oom_adj, this level
* is used for both the normal running in background state and the executing
* operations state. */
- public static final int PROCESS_STATE_SERVICE = 7;
+ public static final int PROCESS_STATE_SERVICE = 9;
/** @hide Process is in the background running a receiver. Note that from the
* perspective of oom_adj receivers run at a higher foreground level, but for our
* prioritization here that is not necessary and putting them below services means
* many fewer changes in some process states as they receive broadcasts. */
- public static final int PROCESS_STATE_RECEIVER = 8;
+ public static final int PROCESS_STATE_RECEIVER = 10;
/** @hide Process is in the background but hosts the home activity. */
- public static final int PROCESS_STATE_HOME = 9;
+ public static final int PROCESS_STATE_HOME = 11;
/** @hide Process is in the background but hosts the last shown activity. */
- public static final int PROCESS_STATE_LAST_ACTIVITY = 10;
+ public static final int PROCESS_STATE_LAST_ACTIVITY = 12;
/** @hide Process is being cached for later use and contains activities. */
- public static final int PROCESS_STATE_CACHED_ACTIVITY = 11;
+ public static final int PROCESS_STATE_CACHED_ACTIVITY = 13;
/** @hide Process is being cached for later use and is a client of another cached
* process that contains activities. */
- public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 12;
+ public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 14;
/** @hide Process is being cached for later use and is empty. */
- public static final int PROCESS_STATE_CACHED_EMPTY = 13;
+ public static final int PROCESS_STATE_CACHED_EMPTY = 15;
/** @hide requestType for assist context: only basic information. */
public static final int ASSIST_CONTEXT_BASIC = 0;
@@ -2064,7 +2070,7 @@
return ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE;
} else if (procState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
return ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE;
- } else if (procState >= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ } else if (procState >= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
return ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
} else {
return ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ed05321..3b96fd5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5322,19 +5322,25 @@
private DropBoxManager dropBox;
- public DropBoxReporter() {
- dropBox = (DropBoxManager) getSystemContext().getSystemService(Context.DROPBOX_SERVICE);
- }
+ public DropBoxReporter() {}
@Override
public void addData(String tag, byte[] data, int flags) {
+ ensureInitialized();
dropBox.addData(tag, data, flags);
}
@Override
public void addText(String tag, String data) {
+ ensureInitialized();
dropBox.addText(tag, data);
}
+
+ private synchronized void ensureInitialized() {
+ if (dropBox == null) {
+ dropBox = (DropBoxManager) getSystemContext().getSystemService(Context.DROPBOX_SERVICE);
+ }
+ }
}
public static void main(String[] args) {
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index 5dd02ae..179957d 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -115,6 +115,40 @@
/** @hide */
public static final long WINDOW_HEURISTIC = -1;
+ /**
+ * Flag for alarms: this is to be a stand-alone alarm, that should not be batched with
+ * other alarms.
+ * @hide
+ */
+ public static final int FLAG_STANDALONE = 1<<0;
+
+ /**
+ * Flag for alarms: this alarm would like to wake the device even if it is idle. This
+ * is, for example, an alarm for an alarm clock.
+ * @hide
+ */
+ public static final int FLAG_WAKE_FROM_IDLE = 1<<1;
+
+ /**
+ * Flag for alarms: this alarm would like to still execute even if the device is
+ * idle. This won't bring the device out of idle, just allow this specific alarm to
+ * run. Note that this means the actual time this alarm goes off can be inconsistent
+ * with the time of non-allow-while-idle alarms (it could go earlier than the time
+ * requested by another alarm).
+ *
+ * @hide
+ */
+ public static final int FLAG_ALLOW_WHILE_IDLE = 1<<2;
+
+ /**
+ * Flag for alarms: this alarm marks the point where we would like to come out of idle
+ * mode. It may be moved by the alarm manager to match the first wake-from-idle alarm.
+ * Scheduling an alarm with this flag puts the alarm manager in to idle mode, where it
+ * avoids scheduling any further alarms until the marker alarm is executed.
+ * @hide
+ */
+ public static final int FLAG_IDLE_UNTIL = 1<<3;
+
private final IAlarmManager mService;
private final boolean mAlwaysExact;
@@ -204,7 +238,7 @@
* @see #RTC_WAKEUP
*/
public void set(int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, legacyExactLength(), 0, operation, null, null);
+ setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null);
}
/**
@@ -265,7 +299,8 @@
*/
public void setRepeating(int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, operation, null, null);
+ setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation, null,
+ null);
}
/**
@@ -315,7 +350,7 @@
*/
public void setWindow(int type, long windowStartMillis, long windowLengthMillis,
PendingIntent operation) {
- setImpl(type, windowStartMillis, windowLengthMillis, 0, operation, null, null);
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation, null, null);
}
/**
@@ -353,7 +388,16 @@
* @see #RTC_WAKEUP
*/
public void setExact(int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, operation, null, null);
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null);
+ }
+
+ /**
+ * Schedule an idle-until alarm, which will keep the alarm manager idle until
+ * the given time.
+ * @hide
+ */
+ public void setIdleUntil(int type, long triggerAtMillis, PendingIntent operation) {
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, operation, null, null);
}
/**
@@ -381,18 +425,19 @@
* @see android.content.Intent#filterEquals
*/
public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
- setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, operation, null, info);
+ setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation, null, info);
}
/** @hide */
@SystemApi
public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
PendingIntent operation, WorkSource workSource) {
- setImpl(type, triggerAtMillis, windowMillis, intervalMillis, operation, workSource, null);
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, workSource,
+ null);
}
private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
- PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) {
+ int flags, PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) {
if (triggerAtMillis < 0) {
/* NOTYET
if (mAlwaysExact) {
@@ -405,7 +450,7 @@
}
try {
- mService.set(type, triggerAtMillis, windowMillis, intervalMillis, operation,
+ mService.set(type, triggerAtMillis, windowMillis, intervalMillis, flags, operation,
workSource, alarmClock);
} catch (RemoteException ex) {
}
@@ -506,7 +551,7 @@
*/
public void setInexactRepeating(int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, operation, null, null);
+ setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null, null);
}
/**
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index 3e545f9..abe12dc 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -22,6 +22,7 @@
import android.annotation.AttrRes;
import android.annotation.DrawableRes;
import android.annotation.StringRes;
+import android.annotation.StyleRes;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
@@ -134,6 +135,7 @@
* {@code context}'s theme.
*
* @param context the parent context
+ * @see android.R.styleable#Theme_alertDialogTheme
*/
protected AlertDialog(Context context) {
this(context, 0);
@@ -155,6 +157,7 @@
* {@code context}'s theme.
*
* @param context the parent context
+ * @see android.R.styleable#Theme_alertDialogTheme
*/
protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
this(context, 0);
@@ -187,9 +190,15 @@
* @param themeResId the resource ID of the theme against which to inflate
* this dialog, or {@code 0} to use the parent
* {@code context}'s default alert dialog theme
+ * @see android.R.styleable#Theme_alertDialogTheme
*/
- protected AlertDialog(Context context, @AttrRes int themeResId) {
- super(context, resolveDialogTheme(context, themeResId));
+ protected AlertDialog(Context context, @StyleRes int themeResId) {
+ this(context, themeResId, true);
+ }
+
+ AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
+ super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
+ createContextThemeWrapper);
mWindow.alwaysReadCloseOnTouchAttr();
mAlert = new AlertController(getContext(), this, getWindow());
@@ -428,7 +437,6 @@
public static class Builder {
private final AlertController.AlertParams P;
- private int mThemeResId;
/**
* Creates a builder for an alert dialog that uses the default alert
@@ -473,7 +481,6 @@
public Builder(Context context, int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
- mThemeResId = themeResId;
}
/**
@@ -1075,7 +1082,7 @@
* create and display the dialog.
*/
public AlertDialog create() {
- final AlertDialog dialog = new AlertDialog(P.mContext, mThemeResId);
+ final AlertDialog dialog = new AlertDialog(P.mContext);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
diff --git a/core/java/android/app/AssistAction.java b/core/java/android/app/AssistAction.java
new file mode 100644
index 0000000..eb33542
--- /dev/null
+++ b/core/java/android/app/AssistAction.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Helper class for building a {@link Bundle} representing an action being performed by the user,
+ * to be included in the Bundle generated by {@link Activity#onProvideAssistData}.
+ *
+ * @see Activity#onProvideAssistData
+ */
+public final class AssistAction {
+
+ /**
+ * Key name for the Bundle containing the schema.org representation of
+ * an action performed, and should be stored in the Bundle generated by
+ * {@link Activity#onProvideAssistData}.
+ */
+ public static final String ASSIST_ACTION_KEY = "android:assist_action";
+
+ /** Bundle key to specify the schema.org ID of the content. */
+ public static final String KEY_ID = "@id";
+
+ /** Bundle key to specify the schema.org type of the content. */
+ public static final String KEY_TYPE = "@type";
+
+ /** Bundle key to specify the name of the content. */
+ public static final String KEY_NAME = "name";
+
+ /** Bundle key to specify the description of the content. */
+ public static final String KEY_DESCRIPTION = "description";
+
+ /** Bundle key to specify the URL of the content. */
+ public static final String KEY_URL = "url";
+
+ /** Bundle key to specify the object of an action. */
+ public static final String KEY_ACTION_OBJECT = "object";
+
+ /** Bundle key to specify the action's status. */
+ public static final String KEY_ACTION_STATUS = "actionStatus";
+
+ /** The act of editing by adding an object to a collection. */
+ public static final String TYPE_ADD_ACTION = "AddAction";
+
+ /** The act of bookmarking an object. */
+ public static final String TYPE_BOOKMARK_ACTION = "BookmarkAction";
+
+ /** The act of liking an object. */
+ public static final String TYPE_LIKE_ACTION = "LikeAction";
+
+ /** The act of consuming audio content. */
+ public static final String TYPE_LISTEN_ACTION = "ListenAction";
+
+ /** The act of consuming static visual content. */
+ public static final String TYPE_VIEW_ACTION = "ViewAction";
+
+ /** The act of expressing a desire about the object. */
+ public static final String TYPE_WANT_ACTION = "WantAction";
+
+ /** The act of watching an object. */
+ public static final String TYPE_WATCH_ACTION = "WatchAction";
+
+ /** The status of an active action. */
+ public static final String STATUS_TYPE_ACTIVE = "ActiveActionStatus";
+
+ /** The status of a completed action. */
+ public static final String STATUS_TYPE_COMPLETED = "CompletedActionStatus";
+
+ private AssistAction() {
+ }
+
+ /**
+ * Update the Bundle passed into {@link Activity#onProvideAssistData} with the action Bundle,
+ * built with {@link ActionBuilder}.
+ *
+ * @param assistDataBundle The Bundle provided to {@link Activity#onProvideAssistData}.
+ * @param actionBundle The Bundle representing an schema.org action.
+ */
+ public static void updateAssistData(Bundle assistDataBundle, Bundle actionBundle) {
+ Preconditions.checkNotNull(assistDataBundle);
+ Preconditions.checkNotNull(actionBundle);
+
+ Preconditions.checkNotNull(actionBundle.getString(KEY_TYPE),
+ "The '@type' property is required in the provided actionBundle");
+ assistDataBundle.putParcelable(ASSIST_ACTION_KEY, actionBundle);
+ }
+
+ /**
+ * Builds a {@link Bundle} representing a schema.org entity.
+ */
+ public static final class ThingBuilder {
+ private final Bundle mBundle;
+
+ public ThingBuilder() {
+ mBundle = new Bundle();
+ }
+
+ /**
+ * Sets the name of the content.
+ *
+ * @param name The name of the content.
+ */
+ public ThingBuilder setName(@Nullable String name) {
+ set(KEY_NAME, name);
+ return this;
+ }
+
+ /**
+ * Sets the app URI of the content.
+ *
+ * @param uri The app URI of the content.
+ */
+ public ThingBuilder setUrl(@Nullable Uri uri) {
+ if (uri != null) {
+ set(KEY_URL, uri.toString());
+ }
+ return this;
+ }
+
+ /**
+ * Sets the ID of the content.
+ *
+ * @param id Set the ID of the content.
+ */
+ public ThingBuilder setId(@Nullable String id) {
+ set(KEY_ID, id);
+ return this;
+ }
+
+ /**
+ * Sets the schema.org type of the content.
+ *
+ * @param type The schema.org type.
+ */
+ public ThingBuilder setType(@Nullable String type) {
+ set(KEY_TYPE, type);
+ return this;
+ }
+
+ /**
+ * Sets the optional description of the content.
+ *
+ * @param description The description of the content.
+ */
+ public ThingBuilder setDescription(@Nullable String description) {
+ set(KEY_DESCRIPTION, description);
+ return this;
+ }
+
+ /**
+ * Sets a property of the content.
+ *
+ * @param key The schema.org property. Must not be null.
+ * @param value The value of the schema.org property.
+ * If null, the value will be ignored.
+ */
+ public ThingBuilder set(@NonNull String key, @Nullable String value) {
+ if (value != null) {
+ mBundle.putString(key, value);
+ }
+ return this;
+ }
+
+ /**
+ * Sets a property of the content.
+ *
+ * @param key The schema.org property. Must not be null.
+ * @param value The value of the schema.org property represented as a bundle.
+ * If null, the value will be ignored.
+ */
+ public ThingBuilder set(@NonNull String key, @Nullable Bundle value) {
+ if (value != null) {
+ mBundle.putParcelable(key, value);
+ }
+ return this;
+ }
+
+ /**
+ * Build the {@link Bundle} object representing the schema.org entity.
+ */
+ public Bundle build() {
+ return mBundle;
+ }
+ }
+
+ /**
+ * Builds a {@link Bundle} representing a schema.org action.
+ */
+ public static final class ActionBuilder {
+ private final Bundle mBundle;
+
+ public ActionBuilder() {
+ mBundle = new Bundle();
+ }
+
+ /**
+ * Sets the schema.org type of the action.
+ *
+ * @param type The schema.org type.
+ */
+ public ActionBuilder setType(@Nullable String type) {
+ set(KEY_TYPE, type);
+ return this;
+ }
+
+ /**
+ * Sets the schema.org object of the action.
+ *
+ * @param object The schema.org object of the action.
+ */
+ public ActionBuilder setObject(@Nullable Bundle object) {
+ set(KEY_ACTION_OBJECT, object);
+ return this;
+ }
+
+ /**
+ * Sets a property of the action.
+ *
+ * @param key The schema.org property. Must not be null.
+ * @param value The value of the schema.org property.
+ * If null, the value will be ignored.
+ */
+ public ActionBuilder set(@NonNull String key, @Nullable String value) {
+ if (value != null) {
+ mBundle.putString(key, value);
+ }
+ return this;
+ }
+
+ /**
+ * Sets a property of the action.
+ *
+ * @param key The schema.org property. Must not be null.
+ * @param value The value of the schema.org property represented as a bundle.
+ * If null, the value will be ignored.
+ */
+ public ActionBuilder set(@NonNull String key, @Nullable Bundle value) {
+ if (value != null) {
+ mBundle.putParcelable(key, value);
+ }
+ return this;
+ }
+
+ /**
+ * Build the {@link Bundle} object representing the schema.org action.
+ */
+ public Bundle build() {
+ if (TextUtils.isEmpty(mBundle.getString(KEY_TYPE, null))) {
+ // Defaults to the base action type http://schema.org/Action.
+ setType("Action");
+ }
+
+ return mBundle;
+ }
+ }
+}
diff --git a/core/java/android/app/AssistContent.java b/core/java/android/app/AssistContent.java
index ace4af7..cb1a3f5 100644
--- a/core/java/android/app/AssistContent.java
+++ b/core/java/android/app/AssistContent.java
@@ -51,7 +51,7 @@
/**
* Sets the Intent associated with the content, describing the current top-level context of
* the activity. If this contains a reference to a piece of data related to the activity,
- * be sure to set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} so the accessibilty
+ * be sure to set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} so the accessibility
* service can access it.
*/
public void setIntent(Intent intent) {
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 9defcbe..786a52f 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -20,10 +20,11 @@
import android.annotation.DrawableRes;
import android.annotation.IdRes;
import android.annotation.LayoutRes;
+import android.annotation.NonNull;
import android.annotation.StringRes;
-import com.android.internal.app.WindowDecorActionBar;
import android.annotation.Nullable;
+import android.annotation.StyleRes;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
@@ -56,6 +57,9 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import com.android.internal.R;
+import com.android.internal.app.WindowDecorActionBar;
+
import java.lang.ref.WeakReference;
/**
@@ -130,52 +134,58 @@
};
/**
- * Create a Dialog window that uses the default dialog frame style.
- *
- * @param context The Context the Dialog is to run it. In particular, it
- * uses the window manager and theme in this context to
- * present its UI.
+ * Creates a dialog window that uses the default dialog theme.
+ * <p>
+ * The supplied {@code context} is used to obtain the window manager and
+ * base theme used to present the dialog.
+ *
+ * @param context the context in which the dialog should run
+ * @see android.R.styleable#Theme_dialogTheme
*/
- public Dialog(Context context) {
+ public Dialog(@NonNull Context context) {
this(context, 0, true);
}
/**
- * Create a Dialog window that uses a custom dialog style.
+ * Creates a dialog window that uses a custom dialog style.
+ * <p>
+ * The supplied {@code context} is used to obtain the window manager and
+ * base theme used to present the dialog.
+ * <p>
+ * The supplied {@code theme} is applied on top of the context's theme. See
+ * <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">
+ * Style and Theme Resources</a> for more information about defining and
+ * using styles.
*
- * @param context The Context in which the Dialog should run. In particular, it
- * uses the window manager and theme from this context to
- * present its UI.
- * @param theme A style resource describing the theme to use for the
- * window. See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Style
- * and Theme Resources</a> for more information about defining and using
- * styles. This theme is applied on top of the current theme in
- * <var>context</var>. If 0, the default dialog theme will be used.
+ * @param context the context in which the dialog should run
+ * @param themeResId a style resource describing the theme to use for the
+ * window, or {@code 0} to use the default dialog theme
*/
- public Dialog(Context context, int theme) {
- this(context, theme, true);
+ public Dialog(@NonNull Context context, @StyleRes int themeResId) {
+ this(context, themeResId, true);
}
- Dialog(Context context, int theme, boolean createContextThemeWrapper) {
+ Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
- if (theme == 0) {
- TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
- outValue, true);
- theme = outValue.resourceId;
+ if (themeResId == 0) {
+ final TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
+ themeResId = outValue.resourceId;
}
- mContext = new ContextThemeWrapper(context, theme);
+ mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
- mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
- Window w = new PhoneWindow(mContext);
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+
+ final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
+
mListenersHandler = new ListenersHandler(this);
}
@@ -184,14 +194,13 @@
* @hide
*/
@Deprecated
- protected Dialog(Context context, boolean cancelable,
- Message cancelCallback) {
+ protected Dialog(@NonNull Context context, boolean cancelable, Message cancelCallback) {
this(context);
mCancelable = cancelable;
mCancelMessage = cancelCallback;
}
- protected Dialog(Context context, boolean cancelable,
+ protected Dialog(@NonNull Context context, boolean cancelable,
OnCancelListener cancelListener) {
this(context);
mCancelable = cancelable;
@@ -203,6 +212,7 @@
*
* @return Context The Context used by the Dialog.
*/
+ @NonNull
public final Context getContext() {
return mContext;
}
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index c053c83..e84a8da 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -18,6 +18,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.app.SharedElementCallback.OnSharedElementsReadyListener;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.ResultReceiver;
@@ -140,13 +141,13 @@
} else {
decor.getViewTreeObserver()
.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- decor.getViewTreeObserver().removeOnPreDrawListener(this);
- viewsReady(sharedElements);
- return true;
- }
- });
+ @Override
+ public boolean onPreDraw() {
+ decor.getViewTreeObserver().removeOnPreDrawListener(this);
+ viewsReady(sharedElements);
+ return true;
+ }
+ });
}
}
@@ -383,23 +384,33 @@
}
final Bundle sharedElementState = mSharedElementsBundle;
mSharedElementsBundle = null;
- final View decorView = getDecor();
- if (decorView != null) {
- decorView.getViewTreeObserver()
- .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- decorView.getViewTreeObserver().removeOnPreDrawListener(this);
- startTransition(new Runnable() {
+ OnSharedElementsReadyListener listener = new OnSharedElementsReadyListener() {
+ @Override
+ public void onSharedElementsReady() {
+ final View decorView = getDecor();
+ if (decorView != null) {
+ decorView.getViewTreeObserver()
+ .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
- public void run() {
- startSharedElementTransition(sharedElementState);
+ public boolean onPreDraw() {
+ decorView.getViewTreeObserver().removeOnPreDrawListener(this);
+ startTransition(new Runnable() {
+ @Override
+ public void run() {
+ startSharedElementTransition(sharedElementState);
+ }
+ });
+ return false;
}
});
- return false;
- }
- });
- decorView.invalidate();
+ decorView.invalidate();
+ }
+ }
+ };
+ if (mListener == null) {
+ listener.onSharedElementsReady();
+ } else {
+ mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, listener);
}
}
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index dd3df47..169952ad 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -18,6 +18,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.app.SharedElementCallback.OnSharedElementsReadyListener;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Matrix;
@@ -27,6 +28,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.ResultReceiver;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.view.View;
@@ -408,21 +410,41 @@
if (!mSharedElementNotified) {
mSharedElementNotified = true;
delayCancel();
- mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
- }
- if (!mExitNotified && mExitComplete) {
- mExitNotified = true;
- mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
- mResultReceiver = null; // done talking
- ViewGroup decorView = getDecor();
- if (!mIsReturning && decorView != null) {
- decorView.suppressLayout(false);
+ if (mListener == null) {
+ mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
+ notifyExitComplete();
+ } else {
+ final ResultReceiver resultReceiver = mResultReceiver;
+ final Bundle sharedElementBundle = mSharedElementBundle;
+ mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements,
+ new OnSharedElementsReadyListener() {
+ @Override
+ public void onSharedElementsReady() {
+ resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS,
+ sharedElementBundle);
+ notifyExitComplete();
+ }
+ });
}
- finishIfNecessary();
+ } else {
+ notifyExitComplete();
}
}
}
+ private void notifyExitComplete() {
+ if (!mExitNotified && mExitComplete) {
+ mExitNotified = true;
+ mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
+ mResultReceiver = null; // done talking
+ ViewGroup decorView = getDecor();
+ if (!mIsReturning && decorView != null) {
+ decorView.suppressLayout(false);
+ }
+ finishIfNecessary();
+ }
+ }
+
private void finishIfNecessary() {
if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() ||
mSharedElementsHidden)) {
diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl
index 194082e..d5719f5 100644
--- a/core/java/android/app/IAlarmManager.aidl
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -28,7 +28,7 @@
interface IAlarmManager {
/** windowLength == 0 means exact; windowLength < 0 means the let the OS decide */
void set(int type, long triggerAtTime, long windowLength,
- long interval, in PendingIntent operation, in WorkSource workSource,
+ long interval, int flags, in PendingIntent operation, in WorkSource workSource,
in AlarmManager.AlarmClockInfo alarmClock);
boolean setTime(long millis);
void setTimeZone(String zone);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 33262b3..e2230da 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -76,12 +76,10 @@
boolean matchesCallFilter(in Bundle extras);
boolean isSystemConditionProviderEnabled(String path);
+ int getZenMode();
ZenModeConfig getZenModeConfig();
- boolean setZenModeConfig(in ZenModeConfig config);
- oneway void setZenMode(int mode);
+ boolean setZenModeConfig(in ZenModeConfig config, String reason);
+ oneway void setZenMode(int mode, in Uri conditionId, String reason);
oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
oneway void requestZenModeConditions(in IConditionListener callback, int relevance);
- oneway void setZenModeCondition(in Condition condition);
- oneway void setAutomaticZenModeConditions(in Uri[] conditionIds);
- Condition[] getAutomaticZenModeConditions();
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 479327d..fa61e18 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -20,6 +20,7 @@
import android.app.Notification.Builder;
import android.content.ComponentName;
import android.content.Context;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -27,7 +28,7 @@
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.UserHandle;
-import android.service.notification.Condition;
+import android.provider.Settings.Global;
import android.service.notification.IConditionListener;
import android.service.notification.ZenModeConfig;
import android.util.Log;
@@ -282,10 +283,10 @@
/**
* @hide
*/
- public void setZenMode(int mode) {
+ public void setZenMode(int mode, Uri conditionId, String reason) {
INotificationManager service = getService();
try {
- service.setZenMode(mode);
+ service.setZenMode(mode, conditionId, reason);
} catch (RemoteException e) {
}
}
@@ -293,6 +294,18 @@
/**
* @hide
*/
+ public boolean setZenModeConfig(ZenModeConfig config, String reason) {
+ INotificationManager service = getService();
+ try {
+ return service.setZenModeConfig(config, reason);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * @hide
+ */
public void requestZenModeConditions(IConditionListener listener, int relevance) {
INotificationManager service = getService();
try {
@@ -304,24 +317,22 @@
/**
* @hide
*/
- public void setZenModeCondition(Condition exitCondition) {
+ public int getZenMode() {
INotificationManager service = getService();
try {
- service.setZenModeCondition(exitCondition);
+ return service.getZenMode();
} catch (RemoteException e) {
}
+ return Global.ZEN_MODE_OFF;
}
/**
* @hide
*/
- public Condition getZenModeCondition() {
+ public ZenModeConfig getZenModeConfig() {
INotificationManager service = getService();
try {
- final ZenModeConfig config = service.getZenModeConfig();
- if (config != null) {
- return config.exitCondition;
- }
+ return service.getZenModeConfig();
} catch (RemoteException e) {
}
return null;
diff --git a/core/java/android/app/SharedElementCallback.java b/core/java/android/app/SharedElementCallback.java
index 6ac2401..e58b7fb 100644
--- a/core/java/android/app/SharedElementCallback.java
+++ b/core/java/android/app/SharedElementCallback.java
@@ -221,4 +221,42 @@
}
return view;
}
+
+ /**
+ * Called during an Activity Transition when the shared elements have arrived at the
+ * final location and are ready to be transferred. This method is called for both the
+ * source and destination Activities.
+ * <p>
+ * When the shared elements are ready to be transferred,
+ * {@link OnSharedElementsReadyListener#onSharedElementsReady()}
+ * must be called to trigger the transfer.
+ * <p>
+ * The default behavior is to trigger the transfer immediately.
+ *
+ * @param sharedElementNames The names of the shared elements that are being transferred..
+ * @param sharedElements The shared elements that are part of the View hierarchy.
+ * @param listener The listener to call when the shared elements are ready to be hidden
+ * in the source Activity or shown in the destination Activity.
+ */
+ public void onSharedElementsArrived(List<String> sharedElementNames,
+ List<View> sharedElements, OnSharedElementsReadyListener listener) {
+ listener.onSharedElementsReady();
+ }
+
+ /**
+ * Listener to be called after {@link
+ * SharedElementCallback#onSharedElementsArrived(List, List, OnSharedElementsReadyListener)}
+ * when the shared elements are ready to be hidden in the source Activity and shown in the
+ * destination Activity.
+ */
+ public interface OnSharedElementsReadyListener {
+
+ /**
+ * Call this method during or after the OnSharedElementsReadyListener has been received
+ * in {@link SharedElementCallback#onSharedElementsArrived(List, List,
+ * OnSharedElementsReadyListener)} to indicate that the shared elements are ready to be
+ * hidden in the source and shown in the destination Activity.
+ */
+ void onSharedElementsReady();
+ }
}
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 7acf5f0..022a62c 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -318,7 +318,7 @@
* @param label The label that will both be matched against what the user speaks
* and displayed visually.
* @param index The location of this option within the overall set of options.
- * Can be used to help identify which the option when it is returned from the
+ * Can be used to help identify the option when it is returned from the
* voice interactor.
*/
public Option(CharSequence label, int index) {
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 9151a16..8b79305 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -339,4 +339,30 @@
}
}
}
+
+ /**
+ * Ask the framework which dataset, if any, the given package's data would be
+ * restored from if we were to install it right now.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param packageName The name of the package whose most-suitable dataset we
+ * wish to look up
+ * @return The dataset token from which a restore should be attempted, or zero if
+ * no suitable data is available.
+ *
+ * @hide
+ */
+ @SystemApi
+ public long getAvailableRestoreToken(String packageName) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ return sService.getAvailableRestoreToken(packageName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "getAvailableRestoreToken() couldn't connect");
+ }
+ }
+ return 0;
+ }
}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 8f36dc4..87e4ef1 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -313,4 +313,17 @@
* is being queried.
*/
boolean isBackupServiceActive(int whichUser);
+
+ /**
+ * Ask the framework which dataset, if any, the given package's data would be
+ * restored from if we were to install it right now.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param packageName The name of the package whose most-suitable dataset we
+ * wish to look up
+ * @return The dataset token from which a restore should be attempted, or zero if
+ * no suitable data is available.
+ */
+ long getAvailableRestoreToken(String packageName);
}
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index 93ea299..5dbfa6a 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -36,7 +36,7 @@
/**
* This class provides methods to perform scan related operations for Bluetooth LE devices. An
- * application can scan for a particular type of Bluetotoh LE devices using {@link ScanFilter}. It
+ * application can scan for a particular type of Bluetooth LE devices using {@link ScanFilter}. It
* can also request different types of callbacks for delivering the result.
* <p>
* Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 590d791..044e3e3 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -517,6 +517,38 @@
}
/**
+ * Return if this filter handle all HTTP or HTTPS data URI or not.
+ *
+ * @return True if the filter handle all HTTP or HTTPS data URI. False otherwise.
+ *
+ * This will check if if the Intent action is {@link android.content.Intent#ACTION_VIEW} and
+ * the Intent category is {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent
+ * data scheme is "http" or "https" and that there is no specific host defined.
+ *
+ * @hide
+ */
+ public final boolean handleAllWebDataURI() {
+ return hasWebDataURI() && (countDataAuthorities() == 0);
+ }
+
+ /**
+ * Return if this filter has any HTTP or HTTPS data URI or not.
+ *
+ * @return True if the filter has any HTTP or HTTPS data URI. False otherwise.
+ *
+ * This will check if if the Intent action is {@link android.content.Intent#ACTION_VIEW} and
+ * the Intent category is {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent
+ * data scheme is "http" or "https".
+ *
+ * @hide
+ */
+ public final boolean hasWebDataURI() {
+ return hasAction(Intent.ACTION_VIEW) &&
+ hasCategory(Intent.CATEGORY_BROWSABLE) &&
+ (hasDataScheme(SCHEME_HTTP) || hasDataScheme(SCHEME_HTTPS));
+ }
+
+ /**
* Return if this filter needs to be automatically verified again its data URIs or not.
*
* @return True if the filter needs to be automatically verified. False otherwise.
@@ -530,10 +562,7 @@
* @hide
*/
public final boolean needsVerification() {
- return hasAction(Intent.ACTION_VIEW) &&
- hasCategory(Intent.CATEGORY_BROWSABLE) &&
- (hasDataScheme(SCHEME_HTTP) || hasDataScheme(SCHEME_HTTPS)) &&
- getAutoVerify();
+ return hasWebDataURI() && getAutoVerify();
}
/**
diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.java b/core/java/android/content/pm/IntentFilterVerificationInfo.java
index 28cbaa8..e50b0ff 100644
--- a/core/java/android/content/pm/IntentFilterVerificationInfo.java
+++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java
@@ -36,7 +36,7 @@
/**
* The {@link com.android.server.pm.PackageManagerService} maintains some
- * {@link IntentFilterVerificationInfo}s for each domain / package / class name per user.
+ * {@link IntentFilterVerificationInfo}s for each domain / package name.
*
* @hide
*/
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 303b709..d5461eba 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1318,6 +1318,15 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports high fidelity sensor processing
+ * capabilities.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_HIFI_SENSORS =
+ "android.hardware.sensor.hifi_sensors";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device has a telephony radio with data
* communication support.
*/
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index bdbed75..7523675 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2778,7 +2778,7 @@
}
/**
- * Check if one of the IntentFilter as an action VIEW and a HTTP/HTTPS data URI
+ * Check if one of the IntentFilter as both actions DEFAULT / VIEW and a HTTP/HTTPS data URI
*/
private static boolean hasDomainURLs(Package pkg) {
if (pkg == null || pkg.activities == null) return false;
@@ -2792,8 +2792,10 @@
for (int m=0; m<countFilters; m++) {
ActivityIntentInfo aii = filters.get(m);
if (!aii.hasAction(Intent.ACTION_VIEW)) continue;
+ if (!aii.hasAction(Intent.ACTION_DEFAULT)) continue;
if (aii.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
aii.hasDataScheme(IntentFilter.SCHEME_HTTPS)) {
+ Slog.d(TAG, "hasDomainURLs:true for package:" + pkg.packageName);
return true;
}
}
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index 7b141f0..05f5e90 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -144,9 +144,9 @@
public boolean system;
/**
- * @hide Does the associated IntentFilter needs verification ?
+ * @hide Does the associated IntentFilter comes from a Browser ?
*/
- public boolean filterNeedsVerification;
+ public boolean handleAllWebDataURI;
private ComponentInfo getComponentInfo() {
if (activityInfo != null) return activityInfo;
@@ -288,7 +288,7 @@
resolvePackageName = orig.resolvePackageName;
system = orig.system;
targetUserId = orig.targetUserId;
- filterNeedsVerification = orig.filterNeedsVerification;
+ handleAllWebDataURI = orig.handleAllWebDataURI;
}
public String toString() {
@@ -350,7 +350,7 @@
dest.writeInt(targetUserId);
dest.writeInt(system ? 1 : 0);
dest.writeInt(noResourceId ? 1 : 0);
- dest.writeInt(filterNeedsVerification ? 1 : 0);
+ dest.writeInt(handleAllWebDataURI ? 1 : 0);
}
public static final Creator<ResolveInfo> CREATOR
@@ -396,7 +396,7 @@
targetUserId = source.readInt();
system = source.readInt() != 0;
noResourceId = source.readInt() != 0;
- filterNeedsVerification = source.readInt() != 0;
+ handleAllWebDataURI = source.readInt() != 0;
}
public static class DisplayNameComparator
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index b5b89dd..581fe7f 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -35,34 +35,38 @@
private static final String TAG = "Cursor";
/**
- * @deprecated This is never updated by this class and should not be used
+ * @removed This field should not be used.
*/
- @Deprecated
protected HashMap<Long, Map<String, Object>> mUpdatedRows;
- protected int mPos;
-
/**
- * This must be set to the index of the row ID column by any
- * subclass that wishes to support updates.
- *
- * @deprecated This field should not be used.
+ * @removed This field should not be used.
*/
- @Deprecated
protected int mRowIdColumnIndex;
/**
- * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of
- * the column at {@link #mRowIdColumnIndex} for the current row this cursor is
- * pointing at.
- *
- * @deprecated This field should not be used.
+ * @removed This field should not be used.
*/
- @Deprecated
protected Long mCurrentRowID;
+ /**
+ * @deprecated Use {@link #getPosition()} instead.
+ */
+ @Deprecated
+ protected int mPos;
+
+ /**
+ * @deprecated Use {@link #isClosed()} instead.
+ */
+ @Deprecated
protected boolean mClosed;
+
+ /**
+ * @deprecated Do not use.
+ */
+ @Deprecated
protected ContentResolver mContentResolver;
+
private Uri mNotifyUri;
private final Object mSelfObserverLock = new Object();
@@ -76,18 +80,28 @@
/* -------------------------------------------------------- */
/* These need to be implemented by subclasses */
+ @Override
abstract public int getCount();
+ @Override
abstract public String[] getColumnNames();
+ @Override
abstract public String getString(int column);
+ @Override
abstract public short getShort(int column);
+ @Override
abstract public int getInt(int column);
+ @Override
abstract public long getLong(int column);
+ @Override
abstract public float getFloat(int column);
+ @Override
abstract public double getDouble(int column);
+ @Override
abstract public boolean isNull(int column);
+ @Override
public int getType(int column) {
// Reflects the assumption that all commonly used field types (meaning everything
// but blobs) are convertible to strings so it should be safe to call
@@ -96,6 +110,7 @@
}
// TODO implement getBlob in all cursor types
+ @Override
public byte[] getBlob(int column) {
throw new UnsupportedOperationException("getBlob is not supported");
}
@@ -108,14 +123,17 @@
*
* @return The pre-filled window that backs this cursor, or null if none.
*/
+ @Override
public CursorWindow getWindow() {
return null;
}
+ @Override
public int getColumnCount() {
return getColumnNames().length;
}
+ @Override
public void deactivate() {
onDeactivateOrClose();
}
@@ -129,6 +147,7 @@
mDataSetObservable.notifyInvalidated();
}
+ @Override
public boolean requery() {
if (mSelfObserver != null && mSelfObserverRegistered == false) {
mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
@@ -138,10 +157,12 @@
return true;
}
+ @Override
public boolean isClosed() {
return mClosed;
}
+ @Override
public void close() {
mClosed = true;
mContentObservable.unregisterAll();
@@ -158,11 +179,13 @@
* @param newPosition the position that we're moving to
* @return true if the move is successful, false otherwise
*/
+ @Override
public boolean onMove(int oldPosition, int newPosition) {
return true;
}
+ @Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
// Default implementation, uses getString
String result = getString(columnIndex);
@@ -183,15 +206,14 @@
/* Implementation */
public AbstractCursor() {
mPos = -1;
- mRowIdColumnIndex = -1;
- mCurrentRowID = null;
- mUpdatedRows = new HashMap<Long, Map<String, Object>>();
}
+ @Override
public final int getPosition() {
return mPos;
}
+ @Override
public final boolean moveToPosition(int position) {
// Make sure position isn't past the end of the cursor
final int count = getCount();
@@ -216,9 +238,6 @@
mPos = -1;
} else {
mPos = position;
- if (mRowIdColumnIndex != -1) {
- mCurrentRowID = Long.valueOf(getLong(mRowIdColumnIndex));
- }
}
return result;
@@ -229,35 +248,43 @@
DatabaseUtils.cursorFillWindow(this, position, window);
}
+ @Override
public final boolean move(int offset) {
return moveToPosition(mPos + offset);
}
+ @Override
public final boolean moveToFirst() {
return moveToPosition(0);
}
+ @Override
public final boolean moveToLast() {
return moveToPosition(getCount() - 1);
}
+ @Override
public final boolean moveToNext() {
return moveToPosition(mPos + 1);
}
+ @Override
public final boolean moveToPrevious() {
return moveToPosition(mPos - 1);
}
+ @Override
public final boolean isFirst() {
return mPos == 0 && getCount() != 0;
}
+ @Override
public final boolean isLast() {
int cnt = getCount();
return mPos == (cnt - 1) && cnt != 0;
}
+ @Override
public final boolean isBeforeFirst() {
if (getCount() == 0) {
return true;
@@ -265,6 +292,7 @@
return mPos == -1;
}
+ @Override
public final boolean isAfterLast() {
if (getCount() == 0) {
return true;
@@ -272,6 +300,7 @@
return mPos == getCount();
}
+ @Override
public int getColumnIndex(String columnName) {
// Hack according to bug 903852
final int periodIndex = columnName.lastIndexOf('.');
@@ -297,6 +326,7 @@
return -1;
}
+ @Override
public int getColumnIndexOrThrow(String columnName) {
final int index = getColumnIndex(columnName);
if (index < 0) {
@@ -305,14 +335,17 @@
return index;
}
+ @Override
public String getColumnName(int columnIndex) {
return getColumnNames()[columnIndex];
}
+ @Override
public void registerContentObserver(ContentObserver observer) {
mContentObservable.registerObserver(observer);
}
+ @Override
public void unregisterContentObserver(ContentObserver observer) {
// cursor will unregister all observers when it close
if (!mClosed) {
@@ -320,10 +353,12 @@
}
}
+ @Override
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
+ @Override
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
@@ -350,6 +385,7 @@
* @param notifyUri The URI to watch for changes. This can be a
* specific row URI, or a base URI for a whole class of content.
*/
+ @Override
public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
setNotificationUri(cr, notifyUri, UserHandle.myUserId());
}
@@ -368,31 +404,29 @@
}
}
+ @Override
public Uri getNotificationUri() {
synchronized (mSelfObserverLock) {
return mNotifyUri;
}
}
+ @Override
public boolean getWantsAllOnMoveCalls() {
return false;
}
- /**
- * Sets a {@link Bundle} that will be returned by {@link #getExtras()}. <code>null</code> will
- * be converted into {@link Bundle#EMPTY}.
- *
- * @param extras {@link Bundle} to set.
- * @hide
- */
+ @Override
public void setExtras(Bundle extras) {
mExtras = (extras == null) ? Bundle.EMPTY : extras;
}
+ @Override
public Bundle getExtras() {
return mExtras;
}
+ @Override
public Bundle respond(Bundle extras) {
return Bundle.EMPTY;
}
diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java
index 98c7043..8576715 100644
--- a/core/java/android/database/BulkCursorToCursorAdaptor.java
+++ b/core/java/android/database/BulkCursorToCursorAdaptor.java
@@ -41,7 +41,6 @@
public void initialize(BulkCursorDescriptor d) {
mBulkCursor = d.cursor;
mColumns = d.columnNames;
- mRowIdColumnIndex = DatabaseUtils.findRowIdColumnIndex(mColumns);
mWantsAllOnMoveCalls = d.wantsAllOnMoveCalls;
mCount = d.count;
if (d.window != null) {
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index fc2a885..d10c9b8 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -444,6 +444,13 @@
boolean getWantsAllOnMoveCalls();
/**
+ * Sets a {@link Bundle} that will be returned by {@link #getExtras()}.
+ *
+ * @param extras {@link Bundle} to set, or null to set an empty bundle.
+ */
+ void setExtras(Bundle extras);
+
+ /**
* Returns a bundle of extra values. This is an optional way for cursors to provide out-of-band
* metadata to their users. One use of this is for reporting on the progress of network requests
* that are required to fetch data for the cursor.
diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java
index d8fcb17..63a2792 100644
--- a/core/java/android/database/CursorWrapper.java
+++ b/core/java/android/database/CursorWrapper.java
@@ -45,163 +45,210 @@
return mCursor;
}
+ @Override
public void close() {
mCursor.close();
}
+ @Override
public boolean isClosed() {
return mCursor.isClosed();
}
+ @Override
public int getCount() {
return mCursor.getCount();
}
+ @Override
+ @Deprecated
public void deactivate() {
mCursor.deactivate();
}
+ @Override
public boolean moveToFirst() {
return mCursor.moveToFirst();
}
+ @Override
public int getColumnCount() {
return mCursor.getColumnCount();
}
+ @Override
public int getColumnIndex(String columnName) {
return mCursor.getColumnIndex(columnName);
}
+ @Override
public int getColumnIndexOrThrow(String columnName)
throws IllegalArgumentException {
return mCursor.getColumnIndexOrThrow(columnName);
}
+ @Override
public String getColumnName(int columnIndex) {
return mCursor.getColumnName(columnIndex);
}
+ @Override
public String[] getColumnNames() {
return mCursor.getColumnNames();
}
+ @Override
public double getDouble(int columnIndex) {
return mCursor.getDouble(columnIndex);
}
+ @Override
+ public void setExtras(Bundle extras) {
+ mCursor.setExtras(extras);
+ }
+
+ @Override
public Bundle getExtras() {
return mCursor.getExtras();
}
+ @Override
public float getFloat(int columnIndex) {
return mCursor.getFloat(columnIndex);
}
+ @Override
public int getInt(int columnIndex) {
return mCursor.getInt(columnIndex);
}
+ @Override
public long getLong(int columnIndex) {
return mCursor.getLong(columnIndex);
}
+ @Override
public short getShort(int columnIndex) {
return mCursor.getShort(columnIndex);
}
+ @Override
public String getString(int columnIndex) {
return mCursor.getString(columnIndex);
}
+ @Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
mCursor.copyStringToBuffer(columnIndex, buffer);
}
+ @Override
public byte[] getBlob(int columnIndex) {
return mCursor.getBlob(columnIndex);
}
+ @Override
public boolean getWantsAllOnMoveCalls() {
return mCursor.getWantsAllOnMoveCalls();
}
+ @Override
public boolean isAfterLast() {
return mCursor.isAfterLast();
}
+ @Override
public boolean isBeforeFirst() {
return mCursor.isBeforeFirst();
}
+ @Override
public boolean isFirst() {
return mCursor.isFirst();
}
+ @Override
public boolean isLast() {
return mCursor.isLast();
}
+ @Override
public int getType(int columnIndex) {
return mCursor.getType(columnIndex);
}
+ @Override
public boolean isNull(int columnIndex) {
return mCursor.isNull(columnIndex);
}
+ @Override
public boolean moveToLast() {
return mCursor.moveToLast();
}
+ @Override
public boolean move(int offset) {
return mCursor.move(offset);
}
+ @Override
public boolean moveToPosition(int position) {
return mCursor.moveToPosition(position);
}
+ @Override
public boolean moveToNext() {
return mCursor.moveToNext();
}
+ @Override
public int getPosition() {
return mCursor.getPosition();
}
+ @Override
public boolean moveToPrevious() {
return mCursor.moveToPrevious();
}
+ @Override
public void registerContentObserver(ContentObserver observer) {
- mCursor.registerContentObserver(observer);
+ mCursor.registerContentObserver(observer);
}
+ @Override
public void registerDataSetObserver(DataSetObserver observer) {
- mCursor.registerDataSetObserver(observer);
+ mCursor.registerDataSetObserver(observer);
}
+ @Override
+ @Deprecated
public boolean requery() {
return mCursor.requery();
}
+ @Override
public Bundle respond(Bundle extras) {
return mCursor.respond(extras);
}
+ @Override
public void setNotificationUri(ContentResolver cr, Uri uri) {
- mCursor.setNotificationUri(cr, uri);
+ mCursor.setNotificationUri(cr, uri);
}
+ @Override
public Uri getNotificationUri() {
return mCursor.getNotificationUri();
}
+ @Override
public void unregisterContentObserver(ContentObserver observer) {
- mCursor.unregisterContentObserver(observer);
+ mCursor.unregisterContentObserver(observer);
}
+ @Override
public void unregisterDataSetObserver(DataSetObserver observer) {
mCursor.unregisterDataSetObserver(observer);
}
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 5a1a8e2..2dc5ca4 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -105,7 +105,6 @@
mQuery = query;
mColumns = query.getColumnNames();
- mRowIdColumnIndex = DatabaseUtils.findRowIdColumnIndex(mColumns);
}
/**
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index f28c96e..bfe4aa2 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1107,8 +1107,7 @@
* that can be configured and used simultaneously by a camera device.</p>
* <p>When set to 0, it means no input stream is supported.</p>
* <p>The image format for a input stream can be any supported
- * format provided by
- * android.scaler.availableInputOutputFormatsMap. When using an
+ * format returned by StreamConfigurationMap#getInputFormats. When using an
* input stream, there must be at least one output stream
* configured to to receive the reprocessed images.</p>
* <p>When an input stream and some output streams are used in a reprocessing request,
@@ -1408,12 +1407,12 @@
* </thead>
* <tbody>
* <tr>
- * <td align="left">OPAQUE</td>
+ * <td align="left">PRIVATE (ImageFormat#PRIVATE)</td>
* <td align="left">JPEG</td>
* <td align="left">OPAQUE_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">OPAQUE</td>
+ * <td align="left">PRIVATE</td>
* <td align="left">YUV_420_888</td>
* <td align="left">OPAQUE_REPROCESSING</td>
* </tr>
@@ -1429,25 +1428,23 @@
* </tr>
* </tbody>
* </table>
- * <p>OPAQUE refers to a device-internal format that is not directly application-visible.
- * An OPAQUE input or output surface can be acquired by
- * OpaqueImageRingBufferQueue#getInputSurface() or
- * OpaqueImageRingBufferQueue#getOutputSurface().
- * For a OPAQUE_REPROCESSING-capable camera device, using the OPAQUE format
+ * <p>PRIVATE refers to a device-internal format that is not directly application-visible.
+ * A PRIVATE input surface can be acquired by
+ * ImageReader.newOpaqueInstance(width, height, maxImages).
+ * For a OPAQUE_REPROCESSING-capable camera device, using the PRIVATE format
* as either input or output will never hurt maximum frame rate (i.e.
- * StreamConfigurationMap#getOutputStallDuration(klass,Size) is always 0),
- * where klass is android.media.OpaqueImageRingBufferQueue.class.</p>
+ * StreamConfigurationMap#getOutputStallDuration(format, size) is always 0),
+ * where format is ImageFormat#PRIVATE.</p>
* <p>Attempting to configure an input stream with output streams not
* listed as available in this map is not valid.</p>
- * <p>TODO: typedef to ReprocessFormatMap</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
*
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
* @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS
* @hide
*/
- public static final Key<int[]> SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP =
- new Key<int[]>("android.scaler.availableInputOutputFormatsMap", int[].class);
+ public static final Key<android.hardware.camera2.params.ReprocessFormatsMap> SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP =
+ new Key<android.hardware.camera2.params.ReprocessFormatsMap>("android.scaler.availableInputOutputFormatsMap", android.hardware.camera2.params.ReprocessFormatsMap.class);
/**
* <p>The available stream configurations that this
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 85c574a..2192ab7 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -448,17 +448,18 @@
* <p>The camera device supports the Zero Shutter Lag reprocessing use case.</p>
* <ul>
* <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li>
- * <li>OPAQUE is supported as an output/input format, that is,
- * StreamConfigurationMap#getOutputSizes(klass) and
- * StreamConfigurationMap#getInputSizes(klass) return non empty Size[] and have common
- * sizes, where klass is android.media.OpaqueImageRingBufferQueue.class. See
- * android.scaler.availableInputOutputFormatsMap for detailed information about
- * OPAQUE format.</li>
- * <li>android.scaler.availableInputOutputFormatsMap has the required map entries.</li>
- * <li>Using OPAQUE does not cause a frame rate drop
+ * <li>ImageFormat#PRIVATE is supported as an output/input format, that is,
+ * ImageFormat#PRIVATE is included in the lists of formats returned by
+ * StreamConfigurationMap#getInputFormats and
+ * StreamConfigurationMap#getOutputFormats.</li>
+ * <li>StreamConfigurationMap#getValidOutputFormatsForInput returns non empty int[] for
+ * each supported input format returned by StreamConfigurationMap#getInputFormats.</li>
+ * <li>Each size returned by StreamConfigurationMap#getInputSizes(ImageFormat#PRIVATE)
+ * is also included in StreamConfigurationMap#getOutputSizes(ImageFormat#PRIVATE)</li>
+ * <li>Using ImageFormat#PRIVATE does not cause a frame rate drop
* relative to the sensor's maximum capture rate (at that
- * resolution), see android.scaler.availableInputOutputFormatsMap for more details.</li>
- * <li>OPAQUE will be reprocessable into both YUV_420_888
+ * resolution).</li>
+ * <li>ImageFormat#PRIVATE will be reprocessable into both YUV_420_888
* and JPEG formats.</li>
* <li>The maximum available resolution for OPAQUE streams
* (both input/output) will match the maximum available
@@ -539,27 +540,29 @@
public static final int REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE = 6;
/**
- * <p>The camera device supports the YUV420_888 reprocessing use case, similar as
+ * <p>The camera device supports the YUV_420_888 reprocessing use case, similar as
* OPAQUE_REPROCESSING, This capability requires the camera device to support the
* following:</p>
* <ul>
* <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li>
- * <li>YUV420_888 is supported as a common format for both input and output, that is,
- * StreamConfigurationMap#getOutputSizes(YUV420_888) and
- * StreamConfigurationMap#getInputSizes(YUV420_888) return non empty Size[] and have
- * common sizes.</li>
- * <li>android.scaler.availableInputOutputFormatsMap has the required map entries.</li>
- * <li>Using YUV420_888 does not cause a frame rate drop
- * relative to the sensor's maximum capture rate (at that
- * resolution), see android.scaler.availableInputOutputFormatsMap for more details.</li>
- * <li>YUV420_888 will be reprocessable into both YUV_420_888
+ * <li>YUV_420_888 is supported as an output/input format, that is,
+ * YUV_420_888 is included in the lists of formats returned by
+ * StreamConfigurationMap#getInputFormats and
+ * StreamConfigurationMap#getOutputFormats.</li>
+ * <li>StreamConfigurationMap#getValidOutputFormatsForInput returns non empty int[] for
+ * each supported input format returned by StreamConfigurationMap#getInputFormats.</li>
+ * <li>Each size returned by StreamConfigurationMap#getInputSizes(YUV_420_888)
+ * is also included in StreamConfigurationMap#getOutputSizes(YUV_420_888)</li>
+ * <li>Using YUV_420_888 does not cause a frame rate drop
+ * relative to the sensor's maximum capture rate (at that resolution).</li>
+ * <li>YUV_420_888 will be reprocessable into both YUV_420_888
* and JPEG formats.</li>
- * <li>The maximum available resolution for YUV420_888 streams
+ * <li>The maximum available resolution for YUV_420_888 streams
* (both input/output) will match the maximum available
* resolution of JPEG streams.</li>
* <li>Only the below controls are effective for reprocessing requests and will be
* present in capture results. The reprocess requests are from the original capture
- * results that are assocaited with the intermidate YUV420_888 output buffers.
+ * results that are assocaited with the intermidate YUV_420_888 output buffers.
* All other controls in the reprocess requests will be ignored by the camera device.<ul>
* <li>android.jpeg.*</li>
* <li>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}</li>
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index e926a98..b8b7d12 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -48,6 +48,7 @@
import android.hardware.camera2.params.Face;
import android.hardware.camera2.params.HighSpeedVideoConfiguration;
import android.hardware.camera2.params.LensShadingMap;
+import android.hardware.camera2.params.ReprocessFormatsMap;
import android.hardware.camera2.params.StreamConfiguration;
import android.hardware.camera2.params.StreamConfigurationDuration;
import android.hardware.camera2.params.StreamConfigurationMap;
@@ -838,11 +839,13 @@
CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS);
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase(
CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS);
+ ReprocessFormatsMap inputOutputFormatsMap = getBase(
+ CameraCharacteristics.SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP);
return new StreamConfigurationMap(
configurations, minFrameDurations, stallDurations,
depthConfigurations, depthMinFrameDurations, depthStallDurations,
- highSpeedVideoConfigurations);
+ highSpeedVideoConfigurations, inputOutputFormatsMap);
}
private <T> Integer getMaxRegions(Key<T> key) {
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index b418971..c231692 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -92,8 +92,8 @@
StreamConfiguration[] depthConfigurations,
StreamConfigurationDuration[] depthMinFrameDurations,
StreamConfigurationDuration[] depthStallDurations,
- HighSpeedVideoConfiguration[] highSpeedVideoConfigurations) {
-
+ HighSpeedVideoConfiguration[] highSpeedVideoConfigurations,
+ ReprocessFormatsMap inputOutputFormatsMap) {
mConfigurations = checkArrayElementsNotNull(configurations, "configurations");
mMinFrameDurations = checkArrayElementsNotNull(minFrameDurations, "minFrameDurations");
mStallDurations = checkArrayElementsNotNull(stallDurations, "stallDurations");
@@ -167,6 +167,8 @@
}
mHighSpeedVideoFpsRangeMap.put(fpsRange, sizeCount + 1);
}
+
+ mInputOutputFormatsMap = inputOutputFormatsMap;
}
/**
@@ -188,6 +190,33 @@
}
/**
+ * Get the image {@code format} output formats for a reprocessing input format.
+ *
+ * <p>When submitting a {@link CaptureRequest} with an input Surface of a given format,
+ * the only allowed target outputs of the {@link CaptureRequest} are the ones with a format
+ * listed in the return value of this method. Including any other output Surface as a target
+ * will throw an IllegalArgumentException. If no output format is supported given the input
+ * format, an empty int[] will be returned.</p>
+ *
+ * <p>All image formats returned by this function will be defined in either {@link ImageFormat}
+ * or in {@link PixelFormat} (and there is no possibility of collision).</p>
+ *
+ * <p>Formats listed in this array are guaranteed to return true if queried with
+ * {@link #isOutputSupportedFor(int)}.</p>
+ *
+ * @return an array of integer format
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ */
+ public final int[] getValidOutputFormatsForInput(int inputFormat) {
+ if (mInputOutputFormatsMap == null) {
+ return new int[0];
+ }
+ return mInputOutputFormatsMap.getOutputs(inputFormat);
+ }
+
+ /**
* Get the image {@code format} input formats in this stream configuration.
*
* <p>All image formats returned by this function will be defined in either {@link ImageFormat}
@@ -197,8 +226,6 @@
*
* @see ImageFormat
* @see PixelFormat
- *
- * @hide
*/
public final int[] getInputFormats() {
return getPublicFormats(/*output*/false);
@@ -212,8 +239,6 @@
*
* @param format a format from {@link #getInputFormats}
* @return a non-empty array of sizes, or {@code null} if the format was not available.
- *
- * @hide
*/
public Size[] getInputSizes(final int format) {
return getPublicFormatSizes(format, /*output*/false);
@@ -390,12 +415,11 @@
* Get a list of sizes compatible with {@code klass} to use as an output.
*
* <p>Since some of the supported classes may support additional formats beyond
- * an opaque/implementation-defined (under-the-hood) format; this function only returns
- * sizes for the implementation-defined format.</p>
- *
- * <p>Some classes such as {@link android.media.ImageReader} may only support user-defined
- * formats; in particular {@link #isOutputSupportedFor(Class)} will return {@code true} for
- * that class and this method will return an empty array (but not {@code null}).</p>
+ * {@link ImageFormat#PRIVATE}; this function only returns
+ * sizes for {@link ImageFormat#PRIVATE}. For example, {@link android.media.ImageReader}
+ * supports {@link ImageFormat#YUV_420_888} and {@link ImageFormat#PRIVATE}, this method will
+ * only return the sizes for {@link ImageFormat#PRIVATE} for {@link android.media.ImageReader}
+ * class .</p>
*
* <p>If a well-defined format such as {@code NV21} is required, use
* {@link #getOutputSizes(int)} instead.</p>
@@ -406,19 +430,15 @@
* @param klass
* a non-{@code null} {@link Class} object reference
* @return
- * an array of supported sizes for implementation-defined formats,
- * or {@code null} iff the {@code klass} is not a supported output
+ * an array of supported sizes for {@link ImageFormat#PRIVATE} format,
+ * or {@code null} iff the {@code klass} is not a supported output.
+ *
*
* @throws NullPointerException if {@code klass} was {@code null}
*
* @see #isOutputSupportedFor(Class)
*/
public <T> Size[] getOutputSizes(Class<T> klass) {
- // Image reader is "supported", but never for implementation-defined formats; return empty
- if (android.media.ImageReader.class.isAssignableFrom(klass)) {
- return new Size[0];
- }
-
if (isOutputSupportedFor(klass) == false) {
return null;
}
@@ -647,7 +667,7 @@
* Get the minimum {@link CaptureRequest#SENSOR_FRAME_DURATION frame duration}
* for the class/size combination (in nanoseconds).
*
- * <p>This assumes a the {@code klass} is set up to use an implementation-defined format.
+ * <p>This assumes a the {@code klass} is set up to use {@link ImageFormat#PRIVATE}.
* For user-defined formats, use {@link #getOutputMinFrameDuration(int, Size)}.</p>
*
* <p>{@code klass} should be one of the ones which is supported by
@@ -791,7 +811,7 @@
/**
* Get the stall duration for the class/size combination (in nanoseconds).
*
- * <p>This assumes a the {@code klass} is set up to use an implementation-defined format.
+ * <p>This assumes a the {@code klass} is set up to use {@link ImageFormat#PRIVATE}.
* For user-defined formats, use {@link #getOutputMinFrameDuration(int, Size)}.</p>
*
* <p>{@code klass} should be one of the ones with a non-empty array returned by
@@ -946,11 +966,11 @@
*
* <p>In particular these formats are converted:
* <ul>
- * <li>HAL_PIXEL_FORMAT_BLOB => ImageFormat.JPEG
+ * <li>HAL_PIXEL_FORMAT_BLOB => ImageFormat.JPEG</li>
* </ul>
* </p>
*
- * <p>Passing in an implementation-defined format which has no public equivalent will fail;
+ * <p>Passing in a format which has no public equivalent will fail;
* as will passing in a public format which has a different internal format equivalent.
* See {@link #checkArgumentFormat} for more details about a legal public format.</p>
*
@@ -977,9 +997,6 @@
case ImageFormat.JPEG:
throw new IllegalArgumentException(
"ImageFormat.JPEG is an unknown internal format");
- case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
- throw new IllegalArgumentException(
- "IMPLEMENTATION_DEFINED must not leak to public API");
default:
return format;
}
@@ -1066,8 +1083,7 @@
* </ul>
* </p>
*
- * <p>Passing in an implementation-defined format here will fail (it's not a public format);
- * as will passing in an internal format which has a different public format equivalent.
+ * <p>Passing in an internal format which has a different public format equivalent will fail.
* See {@link #checkArgumentFormat} for more details about a legal public format.</p>
*
* <p>All other formats are returned as-is, no invalid check is performed.</p>
@@ -1090,9 +1106,6 @@
return HAL_PIXEL_FORMAT_BLOB;
case ImageFormat.DEPTH16:
return HAL_PIXEL_FORMAT_Y16;
- case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
- throw new IllegalArgumentException(
- "IMPLEMENTATION_DEFINED is not allowed via public API");
default:
return format;
}
@@ -1214,8 +1227,7 @@
int i = 0;
for (int format : getFormatsMap(output).keySet()) {
- if (format != HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED &&
- format != HAL_PIXEL_FORMAT_RAW_OPAQUE) {
+ if (format != HAL_PIXEL_FORMAT_RAW_OPAQUE) {
formats[i++] = imageFormatToPublic(format);
}
}
@@ -1280,9 +1292,6 @@
HashMap<Integer, Integer> formatsMap = getFormatsMap(output);
int size = formatsMap.size();
- if (formatsMap.containsKey(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED)) {
- size -= 1;
- }
if (formatsMap.containsKey(HAL_PIXEL_FORMAT_RAW_OPAQUE)) {
size -= 1;
}
@@ -1332,6 +1341,7 @@
private final StreamConfigurationDuration[] mDepthStallDurations;
private final HighSpeedVideoConfiguration[] mHighSpeedVideoConfigurations;
+ private final ReprocessFormatsMap mInputOutputFormatsMap;
/** ImageFormat -> num output sizes mapping */
private final HashMap</*ImageFormat*/Integer, /*Count*/Integer> mOutputFormats =
@@ -1350,3 +1360,4 @@
mHighSpeedVideoFpsRangeMap = new HashMap<Range<Integer>, Integer>();
}
+
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index bd5a3922..cccc4be 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -16,10 +16,12 @@
package android.os;
+import android.content.Context;
import android.os.BatteryProperty;
import android.os.IBatteryPropertiesRegistrar;
import android.os.RemoteException;
import android.os.ServiceManager;
+import com.android.internal.app.IBatteryStats;
/**
* The BatteryManager class contains strings and constants used for values
@@ -128,6 +130,26 @@
public static final int BATTERY_PLUGGED_ANY =
BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS;
+ /**
+ * Sent when the device's battery has started charging (or has reached full charge
+ * and the device is on power). This is a good time to do work that you would like to
+ * avoid doing while on battery (that is to avoid draining the user's battery due to
+ * things they don't care enough about).
+ *
+ * This is paired with {@link #ACTION_DISCHARGING}. The current state can always
+ * be retrieved with {@link #isCharging()}.
+ */
+ public static final String ACTION_CHARGING = "android.os.action.CHARGING";
+
+ /**
+ * Sent when the device's battery may be discharging, so apps should avoid doing
+ * extraneous work that would cause it to discharge faster.
+ *
+ * This is paired with {@link #ACTION_CHARGING}. The current state can always
+ * be retrieved with {@link #isCharging()}.
+ */
+ public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
+
/*
* Battery property identifiers. These must match the values in
* frameworks/native/include/batteryservice/BatteryService.h
@@ -162,17 +184,34 @@
*/
public static final int BATTERY_PROPERTY_ENERGY_COUNTER = 5;
+ private final IBatteryStats mBatteryStats;
private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
/**
* @removed Was previously made visible by accident.
*/
public BatteryManager() {
+ mBatteryStats = IBatteryStats.Stub.asInterface(
+ ServiceManager.getService(BatteryStats.SERVICE_NAME));
mBatteryPropertiesRegistrar = IBatteryPropertiesRegistrar.Stub.asInterface(
ServiceManager.getService("batteryproperties"));
}
/**
+ * Return true if the battery is currently considered to be charging. This means that
+ * the device is plugged in and is supplying sufficient power that the battery level is
+ * going up (or the battery is fully charged). Changes in this state are matched by
+ * broadcasts of {@link #ACTION_CHARGING} and {@link #ACTION_DISCHARGING}.
+ */
+ public boolean isCharging() {
+ try {
+ return mBatteryStats.isCharging();
+ } catch (RemoteException e) {
+ return true;
+ }
+ }
+
+ /**
* Query a battery property from the batteryproperties service.
*
* Returns the requested value, or Long.MIN_VALUE if property not
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 3051926..c7edb1a 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1065,6 +1065,7 @@
public static final int STATE_SCREEN_ON_FLAG = 1<<20;
public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19;
public static final int STATE_PHONE_IN_CALL_FLAG = 1<<18;
+ public static final int STATE_CHARGING_FLAG = 1<<17;
public static final int STATE_BLUETOOTH_ON_FLAG = 1<<16;
public static final int MOST_INTERESTING_STATES =
@@ -1751,6 +1752,7 @@
new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen", "S"),
new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged", "BP"),
new BitDescription(HistoryItem.STATE_PHONE_IN_CALL_FLAG, "phone_in_call", "Pcl"),
+ new BitDescription(HistoryItem.STATE_CHARGING_FLAG, "charging", "ch"),
new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth", "b"),
new BitDescription(HistoryItem.STATE_DATA_CONNECTION_MASK,
HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn", "Pcn",
@@ -1945,6 +1947,13 @@
public static final int NUM_CONTROLLER_ACTIVITY_TYPES = CONTROLLER_POWER_DRAIN + 1;
/**
+ * Returns true if the BatteryStats object has detailed bluetooth power reports.
+ * When true, calling {@link #getBluetoothControllerActivity(int, int)} will yield the
+ * actual power data.
+ */
+ public abstract boolean hasBluetoothActivityReporting();
+
+ /**
* For {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, and
* {@link #CONTROLLER_TX_TIME}, returns the time spent (in milliseconds) in the
* respective state.
@@ -1954,6 +1963,13 @@
public abstract long getBluetoothControllerActivity(int type, int which);
/**
+ * Returns true if the BatteryStats object has detailed WiFi power reports.
+ * When true, calling {@link #getWifiControllerActivity(int, int)} will yield the
+ * actual power data.
+ */
+ public abstract boolean hasWifiActivityReporting();
+
+ /**
* For {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, and
* {@link #CONTROLLER_TX_TIME}, returns the time spent (in milliseconds) in the
* respective state.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5ee8fb3..3087e1d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -880,6 +880,15 @@
"android.settings.VOICE_CONTROL_DO_NOT_DISTURB_MODE";
/**
+ * Activity Action: Show Zen Mode schedule rule configuration settings.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_ZEN_MODE_SCHEDULE_RULE_SETTINGS
+ = "android.settings.ZEN_MODE_SCHEDULE_RULE_SETTINGS";
+
+ /**
* Activity Action: Show the regulatory information screen for the device.
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard
@@ -7218,6 +7227,18 @@
return "ZEN_MODE_OFF";
}
+ /** @hide */ public static boolean isValidZenMode(int value) {
+ switch (value) {
+ case Global.ZEN_MODE_OFF:
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ case Global.ZEN_MODE_ALARMS:
+ case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ return true;
+ default:
+ return false;
+ }
+ }
+
/**
* Opaque value, changes when persisted zen mode configuration changes.
*
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index efbb3b8..4712f97 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -254,7 +255,10 @@
* @param context The context of the app doing the inserting
* @param voicemail Data to be inserted
* @return {@link Uri} of the newly inserted {@link Voicemail}
+ *
+ * @hide
*/
+ @SystemApi
public static Uri insert(Context context, Voicemail voicemail) {
ContentResolver contentResolver = context.getContentResolver();
ContentValues contentValues = getContentValues(voicemail);
@@ -267,7 +271,10 @@
* @param context The context of the app doing the inserting
* @param voicemails Data to be inserted
* @return the number of voicemails inserted
+ *
+ * @hide
*/
+ @SystemApi
public static int insert(Context context, List<Voicemail> voicemails) {
ContentResolver contentResolver = context.getContentResolver();
int count = voicemails.size();
@@ -283,7 +290,10 @@
* package. By default, a package only has permission to delete voicemails it inserted.
*
* @return the number of voicemails deleted
+ *
+ * @hide
*/
+ @SystemApi
public static int deleteAll(Context context) {
return context.getContentResolver().delete(
buildSourceUri(context.getPackageName()), "", new String[0]);
@@ -439,7 +449,10 @@
* @param configurationState See {@link Status#CONFIGURATION_STATE}
* @param dataChannelState See {@link Status#DATA_CHANNEL_STATE}
* @param notificationChannelState See {@link Status#NOTIFICATION_CHANNEL_STATE}
+ *
+ * @hide
*/
+ @SystemApi
public static void setStatus(Context context, PhoneAccountHandle accountHandle,
int configurationState, int dataChannelState, int notificationChannelState) {
ContentResolver contentResolver = context.getContentResolver();
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 5e2accd..e16691c 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -59,9 +59,6 @@
public static final int KM_TAG_BLOB_USAGE_REQUIREMENTS = KM_ENUM | 705;
public static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_LONG | 200;
- public static final int KM_TAG_DSA_GENERATOR = KM_BIGNUM | 201;
- public static final int KM_TAG_DSA_P = KM_BIGNUM | 202;
- public static final int KM_TAG_DSA_Q = KM_BIGNUM | 203;
public static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
public static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
@@ -91,40 +88,17 @@
// Algorithm values.
public static final int KM_ALGORITHM_RSA = 1;
- public static final int KM_ALGORITHM_DSA = 2;
- public static final int KM_ALGORITHM_ECDSA = 3;
- public static final int KM_ALGORITHM_ECIES = 4;
+ public static final int KM_ALGORITHM_EC = 3;
public static final int KM_ALGORITHM_AES = 32;
- public static final int KM_ALGORITHM_3DES = 33;
- public static final int KM_ALGORITHM_SKIPJACK = 34;
- public static final int KM_ALGORITHM_MARS = 48;
- public static final int KM_ALGORITHM_RC6 = 49;
- public static final int KM_ALGORITHM_SERPENT = 50;
- public static final int KM_ALGORITHM_TWOFISH = 51;
- public static final int KM_ALGORITHM_IDEA = 52;
- public static final int KM_ALGORITHM_RC5 = 53;
- public static final int KM_ALGORITHM_CAST5 = 54;
- public static final int KM_ALGORITHM_BLOWFISH = 55;
- public static final int KM_ALGORITHM_RC4 = 64;
- public static final int KM_ALGORITHM_CHACHA20 = 65;
public static final int KM_ALGORITHM_HMAC = 128;
// Block modes.
public static final int KM_MODE_FIRST_UNAUTHENTICATED = 1;
public static final int KM_MODE_ECB = KM_MODE_FIRST_UNAUTHENTICATED;
public static final int KM_MODE_CBC = 2;
- public static final int KM_MODE_CBC_CTS = 3;
public static final int KM_MODE_CTR = 4;
- public static final int KM_MODE_OFB = 5;
- public static final int KM_MODE_CFB = 6;
- public static final int KM_MODE_XTS = 7;
public static final int KM_MODE_FIRST_AUTHENTICATED = 32;
public static final int KM_MODE_GCM = KM_MODE_FIRST_AUTHENTICATED;
- public static final int KM_MODE_OCB = 33;
- public static final int KM_MODE_CCM = 34;
- public static final int KM_MODE_FIRST_MAC = 128;
- public static final int KM_MODE_CMAC = KM_MODE_FIRST_MAC;
- public static final int KM_MODE_POLY1305 = 129;
// Padding modes.
public static final int KM_PAD_NONE = 1;
@@ -132,11 +106,7 @@
public static final int KM_PAD_RSA_PSS = 3;
public static final int KM_PAD_RSA_PKCS1_1_5_ENCRYPT = 4;
public static final int KM_PAD_RSA_PKCS1_1_5_SIGN = 5;
- public static final int KM_PAD_ANSI_X923 = 32;
- public static final int KM_PAD_ISO_10126 = 33;
- public static final int KM_PAD_ZERO = 64;
- public static final int KM_PAD_PKCS7 = 65;
- public static final int KM_PAD_ISO_7816_4 = 66;
+ public static final int KM_PAD_PKCS7 = 64;
// Digest modes.
public static final int KM_DIGEST_NONE = 0;
@@ -146,9 +116,6 @@
public static final int KM_DIGEST_SHA_2_256 = 4;
public static final int KM_DIGEST_SHA_2_384 = 5;
public static final int KM_DIGEST_SHA_2_512 = 6;
- public static final int KM_DIGEST_SHA_3_256 = 7;
- public static final int KM_DIGEST_SHA_3_384 = 8;
- public static final int KM_DIGEST_SHA_3_512 = 9;
// Key origins.
public static final int KM_ORIGIN_HARDWARE = 0;
@@ -168,7 +135,6 @@
// Key formats.
public static final int KM_KEY_FORMAT_X509 = 0;
public static final int KM_KEY_FORMAT_PKCS8 = 1;
- public static final int KM_KEY_FORMAT_PKCS12 = 2;
public static final int KM_KEY_FORMAT_RAW = 3;
// User authenticators.
@@ -219,7 +185,6 @@
public static final int KM_ERROR_INVALID_TAG = -40;
public static final int KM_ERROR_MEMORY_ALLOCATION_FAILED = -41;
public static final int KM_ERROR_INVALID_RESCOPING = -42;
- public static final int KM_ERROR_INVALID_DSA_PARAMS = -43;
public static final int KM_ERROR_IMPORT_PARAMETER_MISMATCH = -44;
public static final int KM_ERROR_SECURE_HW_ACCESS_DENIED = -45;
public static final int KM_ERROR_OPERATION_CANCELLED = -46;
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 2702457..56eb510 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -22,22 +22,25 @@
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.provider.Settings.Global;
import android.text.TextUtils;
import android.text.format.DateFormat;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.R;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Calendar;
import java.util.Locale;
import java.util.Objects;
-
-import com.android.internal.R;
+import java.util.UUID;
/**
* Persisted configuration for zen mode.
@@ -47,10 +50,6 @@
public class ZenModeConfig implements Parcelable {
private static String TAG = "ZenModeConfig";
- public static final String SLEEP_MODE_NIGHTS = "nights";
- public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights";
- public static final String SLEEP_MODE_DAYS_PREFIX = "days:";
-
public static final int SOURCE_ANYONE = 0;
public static final int SOURCE_CONTACT = 1;
public static final int SOURCE_STAR = 2;
@@ -60,6 +59,7 @@
Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY };
public static final int[] WEEKNIGHT_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
Calendar.WEDNESDAY, Calendar.THURSDAY };
+ public static final int[] WEEKEND_DAYS = { Calendar.FRIDAY, Calendar.SATURDAY };
public static final int[] MINUTE_BUCKETS = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 };
private static final int SECONDS_MS = 1000;
@@ -68,24 +68,18 @@
private static final boolean DEFAULT_ALLOW_REMINDERS = true;
private static final boolean DEFAULT_ALLOW_EVENTS = true;
+ private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false;
- private static final int XML_VERSION = 1;
+ private static final int XML_VERSION = 2;
private static final String ZEN_TAG = "zen";
private static final String ZEN_ATT_VERSION = "version";
private static final String ALLOW_TAG = "allow";
private static final String ALLOW_ATT_CALLS = "calls";
+ private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers";
private static final String ALLOW_ATT_MESSAGES = "messages";
private static final String ALLOW_ATT_FROM = "from";
private static final String ALLOW_ATT_REMINDERS = "reminders";
private static final String ALLOW_ATT_EVENTS = "events";
- private static final String SLEEP_TAG = "sleep";
- private static final String SLEEP_ATT_MODE = "mode";
- private static final String SLEEP_ATT_NONE = "none";
-
- private static final String SLEEP_ATT_START_HR = "startHour";
- private static final String SLEEP_ATT_START_MIN = "startMin";
- private static final String SLEEP_ATT_END_HR = "endHour";
- private static final String SLEEP_ATT_END_MIN = "endMin";
private static final String CONDITION_TAG = "condition";
private static final String CONDITION_ATT_COMPONENT = "component";
@@ -97,111 +91,115 @@
private static final String CONDITION_ATT_STATE = "state";
private static final String CONDITION_ATT_FLAGS = "flags";
- private static final String EXIT_CONDITION_TAG = "exitCondition";
- private static final String EXIT_CONDITION_ATT_COMPONENT = "component";
+ private static final String MANUAL_TAG = "manual";
+ private static final String AUTOMATIC_TAG = "automatic";
+
+ private static final String RULE_ATT_ID = "id";
+ private static final String RULE_ATT_ENABLED = "enabled";
+ private static final String RULE_ATT_SNOOZING = "snoozing";
+ private static final String RULE_ATT_NAME = "name";
+ private static final String RULE_ATT_COMPONENT = "component";
+ private static final String RULE_ATT_ZEN = "zen";
+ private static final String RULE_ATT_CONDITION_ID = "conditionId";
public boolean allowCalls;
+ public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS;
public boolean allowMessages;
public boolean allowReminders = DEFAULT_ALLOW_REMINDERS;
public boolean allowEvents = DEFAULT_ALLOW_EVENTS;
public int allowFrom = SOURCE_ANYONE;
- public String sleepMode;
- public int sleepStartHour; // 0-23
- public int sleepStartMinute; // 0-59
- public int sleepEndHour;
- public int sleepEndMinute;
- public boolean sleepNone; // false = priority, true = none
- public ComponentName[] conditionComponents;
- public Uri[] conditionIds;
- public Condition exitCondition;
- public ComponentName exitConditionComponent;
+ public ZenRule manualRule;
+ public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();
public ZenModeConfig() { }
public ZenModeConfig(Parcel source) {
allowCalls = source.readInt() == 1;
+ allowRepeatCallers = source.readInt() == 1;
allowMessages = source.readInt() == 1;
allowReminders = source.readInt() == 1;
allowEvents = source.readInt() == 1;
- if (source.readInt() == 1) {
- sleepMode = source.readString();
- }
- sleepStartHour = source.readInt();
- sleepStartMinute = source.readInt();
- sleepEndHour = source.readInt();
- sleepEndMinute = source.readInt();
- sleepNone = source.readInt() == 1;
- int len = source.readInt();
- if (len > 0) {
- conditionComponents = new ComponentName[len];
- source.readTypedArray(conditionComponents, ComponentName.CREATOR);
- }
- len = source.readInt();
- if (len > 0) {
- conditionIds = new Uri[len];
- source.readTypedArray(conditionIds, Uri.CREATOR);
- }
allowFrom = source.readInt();
- exitCondition = source.readParcelable(null);
- exitConditionComponent = source.readParcelable(null);
+ manualRule = source.readParcelable(null);
+ final int len = source.readInt();
+ if (len > 0) {
+ final String[] ids = new String[len];
+ final ZenRule[] rules = new ZenRule[len];
+ source.readStringArray(ids);
+ source.readTypedArray(rules, ZenRule.CREATOR);
+ for (int i = 0; i < len; i++) {
+ automaticRules.put(ids[i], rules[i]);
+ }
+ }
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(allowCalls ? 1 : 0);
+ dest.writeInt(allowRepeatCallers ? 1 : 0);
dest.writeInt(allowMessages ? 1 : 0);
dest.writeInt(allowReminders ? 1 : 0);
dest.writeInt(allowEvents ? 1 : 0);
- if (sleepMode != null) {
- dest.writeInt(1);
- dest.writeString(sleepMode);
- } else {
- dest.writeInt(0);
- }
- dest.writeInt(sleepStartHour);
- dest.writeInt(sleepStartMinute);
- dest.writeInt(sleepEndHour);
- dest.writeInt(sleepEndMinute);
- dest.writeInt(sleepNone ? 1 : 0);
- if (conditionComponents != null && conditionComponents.length > 0) {
- dest.writeInt(conditionComponents.length);
- dest.writeTypedArray(conditionComponents, 0);
- } else {
- dest.writeInt(0);
- }
- if (conditionIds != null && conditionIds.length > 0) {
- dest.writeInt(conditionIds.length);
- dest.writeTypedArray(conditionIds, 0);
- } else {
- dest.writeInt(0);
- }
dest.writeInt(allowFrom);
- dest.writeParcelable(exitCondition, 0);
- dest.writeParcelable(exitConditionComponent, 0);
+ dest.writeParcelable(manualRule, 0);
+ if (!automaticRules.isEmpty()) {
+ final int len = automaticRules.size();
+ final String[] ids = new String[len];
+ final ZenRule[] rules = new ZenRule[len];
+ for (int i = 0; i < len; i++) {
+ ids[i] = automaticRules.keyAt(i);
+ rules[i] = automaticRules.valueAt(i);
+ }
+ dest.writeInt(len);
+ dest.writeStringArray(ids);
+ dest.writeTypedArray(rules, 0);
+ } else {
+ dest.writeInt(0);
+ }
}
@Override
public String toString() {
return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
.append("allowCalls=").append(allowCalls)
+ .append(",allowRepeatCallers=").append(allowRepeatCallers)
.append(",allowMessages=").append(allowMessages)
.append(",allowFrom=").append(sourceToString(allowFrom))
.append(",allowReminders=").append(allowReminders)
.append(",allowEvents=").append(allowEvents)
- .append(",sleepMode=").append(sleepMode)
- .append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute)
- .append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute)
- .append(",sleepNone=").append(sleepNone)
- .append(",conditionComponents=")
- .append(conditionComponents == null ? null : TextUtils.join(",", conditionComponents))
- .append(",conditionIds=")
- .append(conditionIds == null ? null : TextUtils.join(",", conditionIds))
- .append(",exitCondition=").append(exitCondition)
- .append(",exitConditionComponent=").append(exitConditionComponent)
+ .append(",automaticRules=").append(automaticRules)
+ .append(",manualRule=").append(manualRule)
.append(']').toString();
}
+ public boolean isValid() {
+ if (!isValidManualRule(manualRule)) return false;
+ final int N = automaticRules.size();
+ for (int i = 0; i < N; i++) {
+ if (!isValidAutomaticRule(automaticRules.valueAt(i))) return false;
+ }
+ return true;
+ }
+
+ private static boolean isValidManualRule(ZenRule rule) {
+ return rule == null || Global.isValidZenMode(rule.zenMode) && sameCondition(rule);
+ }
+
+ private static boolean isValidAutomaticRule(ZenRule rule) {
+ return rule != null && !TextUtils.isEmpty(rule.name) && Global.isValidZenMode(rule.zenMode)
+ && rule.conditionId != null && sameCondition(rule);
+ }
+
+ private static boolean sameCondition(ZenRule rule) {
+ if (rule == null) return false;
+ if (rule.conditionId == null) {
+ return rule.condition == null;
+ } else {
+ return rule.condition == null || rule.conditionId.equals(rule.condition.id);
+ }
+ }
+
public static String sourceToString(int source) {
switch (source) {
case SOURCE_ANYONE:
@@ -221,49 +219,34 @@
if (o == this) return true;
final ZenModeConfig other = (ZenModeConfig) o;
return other.allowCalls == allowCalls
+ && other.allowRepeatCallers == allowRepeatCallers
&& other.allowMessages == allowMessages
&& other.allowFrom == allowFrom
&& other.allowReminders == allowReminders
&& other.allowEvents == allowEvents
- && Objects.equals(other.sleepMode, sleepMode)
- && other.sleepNone == sleepNone
- && other.sleepStartHour == sleepStartHour
- && other.sleepStartMinute == sleepStartMinute
- && other.sleepEndHour == sleepEndHour
- && other.sleepEndMinute == sleepEndMinute
- && Objects.deepEquals(other.conditionComponents, conditionComponents)
- && Objects.deepEquals(other.conditionIds, conditionIds)
- && Objects.equals(other.exitCondition, exitCondition)
- && Objects.equals(other.exitConditionComponent, exitConditionComponent);
+ && Objects.equals(other.automaticRules, automaticRules)
+ && Objects.equals(other.manualRule, manualRule);
}
@Override
public int hashCode() {
- return Objects.hash(allowCalls, allowMessages, allowFrom, allowReminders, allowEvents,
- sleepMode, sleepNone, sleepStartHour, sleepStartMinute, sleepEndHour,
- sleepEndMinute, Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds),
- exitCondition, exitConditionComponent);
+ return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowFrom,
+ allowReminders, allowEvents, automaticRules, manualRule);
}
- public boolean isValid() {
- return isValidHour(sleepStartHour) && isValidMinute(sleepStartMinute)
- && isValidHour(sleepEndHour) && isValidMinute(sleepEndMinute)
- && isValidSleepMode(sleepMode);
+ private static String toDayList(int[] days) {
+ if (days == null || days.length == 0) return "";
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < days.length; i++) {
+ if (i > 0) sb.append('.');
+ sb.append(days[i]);
+ }
+ return sb.toString();
}
- public static boolean isValidSleepMode(String sleepMode) {
- return sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS)
- || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS) || tryParseDays(sleepMode) != null;
- }
-
- public static int[] tryParseDays(String sleepMode) {
- if (sleepMode == null) return null;
- sleepMode = sleepMode.trim();
- if (SLEEP_MODE_NIGHTS.equals(sleepMode)) return ALL_DAYS;
- if (SLEEP_MODE_WEEKNIGHTS.equals(sleepMode)) return WEEKNIGHT_DAYS;
- if (!sleepMode.startsWith(SLEEP_MODE_DAYS_PREFIX)) return null;
- if (sleepMode.equals(SLEEP_MODE_DAYS_PREFIX)) return null;
- final String[] tokens = sleepMode.substring(SLEEP_MODE_DAYS_PREFIX.length()).split(",");
+ private static int[] tryParseDayList(String dayList, String sep) {
+ if (dayList == null) return null;
+ final String[] tokens = dayList.split(sep);
if (tokens.length == 0) return null;
final int[] rt = new int[tokens.length];
for (int i = 0; i < tokens.length; i++) {
@@ -283,7 +266,7 @@
}
}
- public static ZenModeConfig readXml(XmlPullParser parser)
+ public static ZenModeConfig readXml(XmlPullParser parser, Migration migration)
throws XmlPullParserException, IOException {
int type = parser.getEventType();
if (type != XmlPullParser.START_TAG) return null;
@@ -291,21 +274,20 @@
if (!ZEN_TAG.equals(tag)) return null;
final ZenModeConfig rt = new ZenModeConfig();
final int version = safeInt(parser, ZEN_ATT_VERSION, XML_VERSION);
- final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>();
- final ArrayList<Uri> conditionIds = new ArrayList<Uri>();
+ if (version == 1) {
+ final XmlV1 v1 = XmlV1.readXml(parser);
+ return migration.migrate(v1);
+ }
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
tag = parser.getName();
if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
- if (!conditionComponents.isEmpty()) {
- rt.conditionComponents = conditionComponents
- .toArray(new ComponentName[conditionComponents.size()]);
- rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]);
- }
return rt;
}
if (type == XmlPullParser.START_TAG) {
if (ALLOW_TAG.equals(tag)) {
rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
+ rt.allowRepeatCallers = safeBoolean(parser, ALLOW_ATT_REPEAT_CALLERS,
+ DEFAULT_ALLOW_REPEAT_CALLERS);
rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
DEFAULT_ALLOW_REMINDERS);
@@ -314,31 +296,13 @@
if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) {
throw new IndexOutOfBoundsException("bad source in config:" + rt.allowFrom);
}
- } else if (SLEEP_TAG.equals(tag)) {
- final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
- rt.sleepMode = isValidSleepMode(mode)? mode : null;
- rt.sleepNone = safeBoolean(parser, SLEEP_ATT_NONE, false);
- final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0);
- final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0);
- final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0);
- final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0);
- rt.sleepStartHour = isValidHour(startHour) ? startHour : 0;
- rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0;
- rt.sleepEndHour = isValidHour(endHour) ? endHour : 0;
- rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0;
- } else if (CONDITION_TAG.equals(tag)) {
- final ComponentName component =
- safeComponentName(parser, CONDITION_ATT_COMPONENT);
- final Uri conditionId = safeUri(parser, CONDITION_ATT_ID);
- if (component != null && conditionId != null) {
- conditionComponents.add(component);
- conditionIds.add(conditionId);
- }
- } else if (EXIT_CONDITION_TAG.equals(tag)) {
- rt.exitCondition = readConditionXml(parser);
- if (rt.exitCondition != null) {
- rt.exitConditionComponent =
- safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT);
+ } else if (MANUAL_TAG.equals(tag)) {
+ rt.manualRule = readRuleXml(parser);
+ } else if (AUTOMATIC_TAG.equals(tag)) {
+ final String id = parser.getAttributeValue(null, RULE_ATT_ID);
+ final ZenRule automaticRule = readRuleXml(parser);
+ if (id != null && automaticRule != null) {
+ rt.automaticRules.put(id, automaticRule);
}
}
}
@@ -352,45 +316,68 @@
out.startTag(null, ALLOW_TAG);
out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls));
+ out.attribute(null, ALLOW_ATT_REPEAT_CALLERS, Boolean.toString(allowRepeatCallers));
out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages));
out.attribute(null, ALLOW_ATT_REMINDERS, Boolean.toString(allowReminders));
out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents));
out.attribute(null, ALLOW_ATT_FROM, Integer.toString(allowFrom));
out.endTag(null, ALLOW_TAG);
- out.startTag(null, SLEEP_TAG);
- if (sleepMode != null) {
- out.attribute(null, SLEEP_ATT_MODE, sleepMode);
+ if (manualRule != null) {
+ out.startTag(null, MANUAL_TAG);
+ writeRuleXml(manualRule, out);
+ out.endTag(null, MANUAL_TAG);
}
- out.attribute(null, SLEEP_ATT_NONE, Boolean.toString(sleepNone));
- out.attribute(null, SLEEP_ATT_START_HR, Integer.toString(sleepStartHour));
- out.attribute(null, SLEEP_ATT_START_MIN, Integer.toString(sleepStartMinute));
- out.attribute(null, SLEEP_ATT_END_HR, Integer.toString(sleepEndHour));
- out.attribute(null, SLEEP_ATT_END_MIN, Integer.toString(sleepEndMinute));
- out.endTag(null, SLEEP_TAG);
-
- if (conditionComponents != null && conditionIds != null
- && conditionComponents.length == conditionIds.length) {
- for (int i = 0; i < conditionComponents.length; i++) {
- out.startTag(null, CONDITION_TAG);
- out.attribute(null, CONDITION_ATT_COMPONENT,
- conditionComponents[i].flattenToString());
- out.attribute(null, CONDITION_ATT_ID, conditionIds[i].toString());
- out.endTag(null, CONDITION_TAG);
- }
- }
- if (exitCondition != null && exitConditionComponent != null) {
- out.startTag(null, EXIT_CONDITION_TAG);
- out.attribute(null, EXIT_CONDITION_ATT_COMPONENT,
- exitConditionComponent.flattenToString());
- writeConditionXml(exitCondition, out);
- out.endTag(null, EXIT_CONDITION_TAG);
+ final int N = automaticRules.size();
+ for (int i = 0; i < N; i++) {
+ final String id = automaticRules.keyAt(i);
+ final ZenRule automaticRule = automaticRules.valueAt(i);
+ out.startTag(null, AUTOMATIC_TAG);
+ out.attribute(null, RULE_ATT_ID, id);
+ writeRuleXml(automaticRule, out);
+ out.endTag(null, AUTOMATIC_TAG);
}
out.endTag(null, ZEN_TAG);
}
+ public static ZenRule readRuleXml(XmlPullParser parser) {
+ final ZenRule rt = new ZenRule();
+ rt.enabled = safeBoolean(parser, RULE_ATT_ENABLED, true);
+ rt.snoozing = safeBoolean(parser, RULE_ATT_SNOOZING, false);
+ rt.name = parser.getAttributeValue(null, RULE_ATT_NAME);
+ final String zen = parser.getAttributeValue(null, RULE_ATT_ZEN);
+ rt.zenMode = tryParseZenMode(zen, -1);
+ if (rt.zenMode == -1) {
+ Slog.w(TAG, "Bad zen mode in rule xml:" + zen);
+ return null;
+ }
+ rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID);
+ rt.component = safeComponentName(parser, RULE_ATT_COMPONENT);
+ rt.condition = readConditionXml(parser);
+ return rt.condition != null ? rt : null;
+ }
+
+ public static void writeRuleXml(ZenRule rule, XmlSerializer out) throws IOException {
+ out.attribute(null, RULE_ATT_ENABLED, Boolean.toString(rule.enabled));
+ out.attribute(null, RULE_ATT_SNOOZING, Boolean.toString(rule.snoozing));
+ if (rule.name != null) {
+ out.attribute(null, RULE_ATT_NAME, rule.name);
+ }
+ out.attribute(null, RULE_ATT_ZEN, Integer.toString(rule.zenMode));
+ if (rule.component != null) {
+ out.attribute(null, RULE_ATT_COMPONENT, rule.component.flattenToString());
+ }
+ if (rule.conditionId != null) {
+ out.attribute(null, RULE_ATT_CONDITION_ID, rule.conditionId.toString());
+ }
+ if (rule.condition != null) {
+ writeConditionXml(rule.condition, out);
+ }
+ }
+
public static Condition readConditionXml(XmlPullParser parser) {
final Uri id = safeUri(parser, CONDITION_ATT_ID);
+ if (id == null) return null;
final String summary = parser.getAttributeValue(null, CONDITION_ATT_SUMMARY);
final String line1 = parser.getAttributeValue(null, CONDITION_ATT_LINE1);
final String line2 = parser.getAttributeValue(null, CONDITION_ATT_LINE2);
@@ -446,6 +433,14 @@
return Uri.parse(val);
}
+ public ArraySet<String> getAutomaticRuleNames() {
+ final ArraySet<String> rt = new ArraySet<String>();
+ for (int i = 0; i < automaticRules.size(); i++) {
+ rt.add(automaticRules.valueAt(i).name);
+ }
+ return rt;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -475,17 +470,6 @@
}
};
- public DowntimeInfo toDowntimeInfo() {
- final DowntimeInfo downtime = new DowntimeInfo();
- downtime.startHour = sleepStartHour;
- downtime.startMinute = sleepStartMinute;
- downtime.endHour = sleepEndHour;
- downtime.endMinute = sleepEndMinute;
- downtime.mode = sleepMode;
- downtime.none = sleepNone;
- return downtime;
- }
-
public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) {
final long now = System.currentTimeMillis();
final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS;
@@ -548,38 +532,77 @@
return tryParseCountdownConditionId(conditionId) != 0;
}
- // Built-in downtime conditions
- // e.g. condition://android/downtime?start=10.00&end=7.00&mode=days%3A5%2C6&none=false
- public static final String DOWNTIME_PATH = "downtime";
+ // built-in schedule conditions
+ public static final String SCHEDULE_PATH = "schedule";
- public static Uri toDowntimeConditionId(DowntimeInfo downtime) {
+ public static class ScheduleInfo {
+ public int[] days;
+ public int startHour;
+ public int startMinute;
+ public int endHour;
+ public int endMinute;
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ScheduleInfo)) return false;
+ final ScheduleInfo other = (ScheduleInfo) o;
+ return toDayList(days).equals(toDayList(other.days))
+ && startHour == other.startHour
+ && startMinute == other.startMinute
+ && endHour == other.endHour
+ && endMinute == other.endMinute;
+ }
+
+ public ScheduleInfo copy() {
+ final ScheduleInfo rt = new ScheduleInfo();
+ if (days != null) {
+ rt.days = new int[days.length];
+ System.arraycopy(days, 0, rt.days, 0, days.length);
+ }
+ rt.startHour = startHour;
+ rt.startMinute = startMinute;
+ rt.endHour = endHour;
+ rt.endMinute = endMinute;
+ return rt;
+ }
+ }
+
+ public static Uri toScheduleConditionId(ScheduleInfo schedule) {
return new Uri.Builder().scheme(Condition.SCHEME)
.authority(SYSTEM_AUTHORITY)
- .appendPath(DOWNTIME_PATH)
- .appendQueryParameter("start", downtime.startHour + "." + downtime.startMinute)
- .appendQueryParameter("end", downtime.endHour + "." + downtime.endMinute)
- .appendQueryParameter("mode", downtime.mode)
- .appendQueryParameter("none", Boolean.toString(downtime.none))
+ .appendPath(SCHEDULE_PATH)
+ .appendQueryParameter("days", toDayList(schedule.days))
+ .appendQueryParameter("start", schedule.startHour + "." + schedule.startMinute)
+ .appendQueryParameter("end", schedule.endHour + "." + schedule.endMinute)
.build();
}
- public static DowntimeInfo tryParseDowntimeConditionId(Uri conditionId) {
- if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY)
- || conditionId.getPathSegments().size() != 1
- || !DOWNTIME_PATH.equals(conditionId.getPathSegments().get(0))) {
- return null;
- }
+ public static boolean isValidScheduleConditionId(Uri conditionId) {
+ return tryParseScheduleConditionId(conditionId) != null;
+ }
+
+ public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) {
+ final boolean isSchedule = conditionId != null
+ && conditionId.getScheme().equals(Condition.SCHEME)
+ && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
+ && conditionId.getPathSegments().size() == 1
+ && conditionId.getPathSegments().get(0).equals(ZenModeConfig.SCHEDULE_PATH);
+ if (!isSchedule) return null;
final int[] start = tryParseHourAndMinute(conditionId.getQueryParameter("start"));
final int[] end = tryParseHourAndMinute(conditionId.getQueryParameter("end"));
if (start == null || end == null) return null;
- final DowntimeInfo downtime = new DowntimeInfo();
- downtime.startHour = start[0];
- downtime.startMinute = start[1];
- downtime.endHour = end[0];
- downtime.endMinute = end[1];
- downtime.mode = conditionId.getQueryParameter("mode");
- downtime.none = Boolean.toString(true).equals(conditionId.getQueryParameter("none"));
- return downtime;
+ final ScheduleInfo rt = new ScheduleInfo();
+ rt.days = tryParseDayList(conditionId.getQueryParameter("days"), "\\.");
+ rt.startHour = start[0];
+ rt.startMinute = start[1];
+ rt.endHour = end[0];
+ rt.endMinute = end[1];
+ return rt;
}
private static int[] tryParseHourAndMinute(String value) {
@@ -591,36 +614,268 @@
return isValidHour(hour) && isValidMinute(minute) ? new int[] { hour, minute } : null;
}
- public static boolean isValidDowntimeConditionId(Uri conditionId) {
- return tryParseDowntimeConditionId(conditionId) != null;
+ private static int tryParseZenMode(String value, int defValue) {
+ final int rt = tryParseInt(value, defValue);
+ return Global.isValidZenMode(rt) ? rt : defValue;
}
- public static class DowntimeInfo {
- public int startHour; // 0-23
- public int startMinute; // 0-59
- public int endHour;
- public int endMinute;
- public String mode;
- public boolean none;
+ public String newRuleId() {
+ return UUID.randomUUID().toString().replace("-", "");
+ }
+
+ public static String getConditionLine1(Context context, ZenModeConfig config,
+ int userHandle) {
+ return getConditionLine(context, config, userHandle, true /*useLine1*/);
+ }
+
+ public static String getConditionSummary(Context context, ZenModeConfig config,
+ int userHandle) {
+ return getConditionLine(context, config, userHandle, false /*useLine1*/);
+ }
+
+ private static String getConditionLine(Context context, ZenModeConfig config,
+ int userHandle, boolean useLine1) {
+ if (config == null) return "";
+ if (config.manualRule != null) {
+ final Uri id = config.manualRule.conditionId;
+ if (id == null) {
+ return context.getString(com.android.internal.R.string.zen_mode_forever);
+ }
+ final long time = tryParseCountdownConditionId(id);
+ Condition c = config.manualRule.condition;
+ if (time > 0) {
+ final long now = System.currentTimeMillis();
+ final long span = time - now;
+ c = toTimeCondition(context,
+ time, Math.round(span / (float) MINUTES_MS), now, userHandle);
+ }
+ final String rt = c == null ? "" : useLine1 ? c.line1 : c.summary;
+ return TextUtils.isEmpty(rt) ? "" : rt;
+ }
+ String summary = "";
+ for (ZenRule automaticRule : config.automaticRules.values()) {
+ if (automaticRule.enabled && !automaticRule.snoozing
+ && automaticRule.isTrueOrUnknown()) {
+ if (summary.isEmpty()) {
+ summary = automaticRule.name;
+ } else {
+ summary = context.getResources()
+ .getString(R.string.zen_mode_rule_name_combination, summary,
+ automaticRule.name);
+ }
+ }
+ }
+ return summary;
+ }
+
+ public static class ZenRule implements Parcelable {
+ public boolean enabled;
+ public boolean snoozing; // user manually disabled this instance
+ public String name; // required for automatic (unique)
+ public int zenMode;
+ public Uri conditionId; // required for automatic
+ public Condition condition; // optional
+ public ComponentName component; // optional
+
+ public ZenRule() { }
+
+ public ZenRule(Parcel source) {
+ enabled = source.readInt() == 1;
+ snoozing = source.readInt() == 1;
+ if (source.readInt() == 1) {
+ name = source.readString();
+ }
+ zenMode = source.readInt();
+ conditionId = source.readParcelable(null);
+ condition = source.readParcelable(null);
+ component = source.readParcelable(null);
+ }
@Override
- public int hashCode() {
+ public int describeContents() {
return 0;
}
@Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(enabled ? 1 : 0);
+ dest.writeInt(snoozing ? 1 : 0);
+ if (name != null) {
+ dest.writeInt(1);
+ dest.writeString(name);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(zenMode);
+ dest.writeParcelable(conditionId, 0);
+ dest.writeParcelable(condition, 0);
+ dest.writeParcelable(component, 0);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder(ZenRule.class.getSimpleName()).append('[')
+ .append("enabled=").append(enabled)
+ .append(",snoozing=").append(snoozing)
+ .append(",name=").append(name)
+ .append(",zenMode=").append(Global.zenModeToString(zenMode))
+ .append(",conditionId=").append(conditionId)
+ .append(",condition=").append(condition)
+ .append(",component=").append(component)
+ .append(']').toString();
+ }
+
+ @Override
public boolean equals(Object o) {
- if (!(o instanceof DowntimeInfo)) return false;
- final DowntimeInfo other = (DowntimeInfo) o;
- return startHour == other.startHour
- && startMinute == other.startMinute
- && endHour == other.endHour
- && endMinute == other.endMinute
- && Objects.equals(mode, other.mode)
- && none == other.none;
+ if (!(o instanceof ZenRule)) return false;
+ if (o == this) return true;
+ final ZenRule other = (ZenRule) o;
+ return other.enabled == enabled
+ && other.snoozing == snoozing
+ && Objects.equals(other.name, name)
+ && other.zenMode == zenMode
+ && Objects.equals(other.conditionId, conditionId)
+ && Objects.equals(other.condition, condition)
+ && Objects.equals(other.component, component);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
+ component);
+ }
+
+ public boolean isTrueOrUnknown() {
+ return condition == null || condition.state == Condition.STATE_TRUE
+ || condition.state == Condition.STATE_UNKNOWN;
+ }
+
+ public static final Parcelable.Creator<ZenRule> CREATOR
+ = new Parcelable.Creator<ZenRule>() {
+ @Override
+ public ZenRule createFromParcel(Parcel source) {
+ return new ZenRule(source);
+ }
+ @Override
+ public ZenRule[] newArray(int size) {
+ return new ZenRule[size];
+ }
+ };
+ }
+
+ // Legacy config
+ public static final class XmlV1 {
+ public static final String SLEEP_MODE_NIGHTS = "nights";
+ public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights";
+ public static final String SLEEP_MODE_DAYS_PREFIX = "days:";
+
+ private static final String EXIT_CONDITION_TAG = "exitCondition";
+ private static final String EXIT_CONDITION_ATT_COMPONENT = "component";
+ private static final String SLEEP_TAG = "sleep";
+ private static final String SLEEP_ATT_MODE = "mode";
+ private static final String SLEEP_ATT_NONE = "none";
+
+ private static final String SLEEP_ATT_START_HR = "startHour";
+ private static final String SLEEP_ATT_START_MIN = "startMin";
+ private static final String SLEEP_ATT_END_HR = "endHour";
+ private static final String SLEEP_ATT_END_MIN = "endMin";
+
+ public boolean allowCalls;
+ public boolean allowMessages;
+ public boolean allowReminders = DEFAULT_ALLOW_REMINDERS;
+ public boolean allowEvents = DEFAULT_ALLOW_EVENTS;
+ public int allowFrom = SOURCE_ANYONE;
+
+ public String sleepMode; // nights, weeknights, days:1,2,3 Calendar.days
+ public int sleepStartHour; // 0-23
+ public int sleepStartMinute; // 0-59
+ public int sleepEndHour;
+ public int sleepEndMinute;
+ public boolean sleepNone; // false = priority, true = none
+ public ComponentName[] conditionComponents;
+ public Uri[] conditionIds;
+ public Condition exitCondition; // manual exit condition
+ public ComponentName exitConditionComponent; // manual exit condition component
+
+ private static boolean isValidSleepMode(String sleepMode) {
+ return sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS)
+ || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS) || tryParseDays(sleepMode) != null;
+ }
+
+ public static int[] tryParseDays(String sleepMode) {
+ if (sleepMode == null) return null;
+ sleepMode = sleepMode.trim();
+ if (SLEEP_MODE_NIGHTS.equals(sleepMode)) return ALL_DAYS;
+ if (SLEEP_MODE_WEEKNIGHTS.equals(sleepMode)) return WEEKNIGHT_DAYS;
+ if (!sleepMode.startsWith(SLEEP_MODE_DAYS_PREFIX)) return null;
+ if (sleepMode.equals(SLEEP_MODE_DAYS_PREFIX)) return null;
+ return tryParseDayList(sleepMode.substring(SLEEP_MODE_DAYS_PREFIX.length()), ",");
+ }
+
+ public static XmlV1 readXml(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int type;
+ String tag;
+ XmlV1 rt = new XmlV1();
+ final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>();
+ final ArrayList<Uri> conditionIds = new ArrayList<Uri>();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ tag = parser.getName();
+ if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
+ if (!conditionComponents.isEmpty()) {
+ rt.conditionComponents = conditionComponents
+ .toArray(new ComponentName[conditionComponents.size()]);
+ rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]);
+ }
+ return rt;
+ }
+ if (type == XmlPullParser.START_TAG) {
+ if (ALLOW_TAG.equals(tag)) {
+ rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
+ rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
+ rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
+ DEFAULT_ALLOW_REMINDERS);
+ rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS,
+ DEFAULT_ALLOW_EVENTS);
+ rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE);
+ if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) {
+ throw new IndexOutOfBoundsException("bad source in config:"
+ + rt.allowFrom);
+ }
+ } else if (SLEEP_TAG.equals(tag)) {
+ final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
+ rt.sleepMode = isValidSleepMode(mode)? mode : null;
+ rt.sleepNone = safeBoolean(parser, SLEEP_ATT_NONE, false);
+ final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0);
+ final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0);
+ final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0);
+ final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0);
+ rt.sleepStartHour = isValidHour(startHour) ? startHour : 0;
+ rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0;
+ rt.sleepEndHour = isValidHour(endHour) ? endHour : 0;
+ rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0;
+ } else if (CONDITION_TAG.equals(tag)) {
+ final ComponentName component =
+ safeComponentName(parser, CONDITION_ATT_COMPONENT);
+ final Uri conditionId = safeUri(parser, CONDITION_ATT_ID);
+ if (component != null && conditionId != null) {
+ conditionComponents.add(component);
+ conditionIds.add(conditionId);
+ }
+ } else if (EXIT_CONDITION_TAG.equals(tag)) {
+ rt.exitCondition = readConditionXml(parser);
+ if (rt.exitCondition != null) {
+ rt.exitConditionComponent =
+ safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT);
+ }
+ }
+ }
+ }
+ throw new IllegalStateException("Failed to reach END_DOCUMENT");
}
}
- // built-in next alarm conditions
- public static final String NEXT_ALARM_PATH = "next_alarm";
+ public interface Migration {
+ ZenModeConfig migrate(XmlV1 v1);
+ }
}
diff --git a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
index 52db223..0071a33 100644
--- a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
+++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
@@ -16,6 +16,8 @@
package android.service.persistentdata;
+import android.app.PendingIntent;
+import android.os.Bundle;
import android.os.ParcelFileDescriptor;
/**
@@ -30,6 +32,7 @@
int write(in byte[] data);
byte[] read();
void wipe();
+ void wipeIfAllowed(in Bundle bundle, in PendingIntent pi);
int getDataBlockSize();
long getMaximumDataBlockSize();
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
index 0ffdf68..31570c6 100644
--- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -17,6 +17,8 @@
package android.service.persistentdata;
import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
@@ -41,6 +43,56 @@
@SystemApi
public class PersistentDataBlockManager {
private static final String TAG = PersistentDataBlockManager.class.getSimpleName();
+
+ /**
+ * Broadcast action that will be called when the {@link #wipeIfAllowed(Bundle,PendingIntent)}
+ * method is called. A broadcast with this action will be sent to the package allowed to write
+ * to the persistent data block. Packages receiving this broadcasts should respond by using the
+ * {@link android.app.PendingIntent} sent in the {@link #EXTRA_WIPE_IF_ALLOWED_CALLBACK} extra.
+ */
+ public static final String ACTION_WIPE_IF_ALLOWED
+ = "android.service.persistentdata.action.WIPE_IF_ALLOWED";
+
+ /**
+ * A {@link android.os.Parcelable} extra of type {@link android.app.PendingIntent} used to
+ * response to {@link #wipeIfAllowed(Bundle,PendingIntent)}. This extra will set in broadcasts
+ * with an action of {@link #ACTION_WIPE_IF_ALLOWED}.
+ */
+ public static final String EXTRA_WIPE_IF_ALLOWED_CALLBACK
+ = "android.service.persistentdata.extra.WIPE_IF_ALLOWED_CALLBACK";
+
+ /**
+ * Result code indicating that the data block was wiped.
+ *
+ * <p>This value is set as result code of the {@link android.app.PendingIntent} argument to
+ * {@link #wipeIfAllowed(Bundle,PendingIntent)}
+ */
+ public static final int STATUS_SUCCESS = 0;
+
+ /**
+ * Result code indicating that a remote exception was received while processing the request.
+ *
+ * <p>This value is set as result code of the {@link android.app.PendingIntent} argument to
+ * {@link #wipeIfAllowed(Bundle,PendingIntent)}
+ */
+ public static final int STATUS_ERROR_REMOTE_EXCEPTION = 1;
+
+ /**
+ * Result code indicating that a network error occurred while processing the request.
+ *
+ * <p>This value is set as result code of the {@link android.app.PendingIntent} argument to
+ * {@link #wipeIfAllowed(Bundle,PendingIntent)}
+ */
+ public static final int STATUS_ERROR_NETWORK_ERROR = 2;
+
+ /**
+ * Result code indicating that the data block could not be cleared with the provided data.
+ *
+ * <p>This value is set as result code of the {@link android.app.PendingIntent} argument to
+ * {@link #wipeIfAllowed(Bundle,PendingIntent)}
+ */
+ public static final int STATUS_ERROR_NOT_COMPLIANT = 3;
+
private IPersistentDataBlockService sService;
public PersistentDataBlockManager(IPersistentDataBlockService service) {
@@ -118,6 +170,28 @@
}
/**
+ * Attempt to wipe the data block by sending a broadcast to the package allowed to modify the
+ * datablock. The allowed package can refuse to wipe the data block based on the contents of
+ * the specified bundle. This bundle may contain data used by the allowed package to wipe the
+ * partition such as account credentials or an authorization token.
+ * @param bundle data used to wipe the data block. The contents of this bundle depend on the
+ * allowed package receiving the data.
+ * @param pi intent called when attempt finished. The result code of this intent will be set
+ * to one of {@link #STATUS_SUCCESS}, {@link #STATUS_ERROR_REMOTE_EXCEPTION},
+ * {@link #STATUS_ERROR_NETWORK_ERROR}, or {@link #STATUS_ERROR_NOT_COMPLIANT}.
+ */
+ public void wipeIfAllowed(Bundle bundle, PendingIntent pi) {
+ if (pi == null) {
+ throw new NullPointerException();
+ }
+ try {
+ sService.wipeIfAllowed(bundle, pi);
+ } catch (RemoteException e) {
+ onError("wiping persistent partition");
+ }
+ }
+
+ /**
* Writes a byte enabling or disabling the ability to "OEM unlock" the device.
*/
public void setOemUnlockEnabled(boolean enabled) {
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 16ae5e2..60de02a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -383,6 +383,7 @@
tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);
tl.draw(canvas, x, ltop, lbaseline, lbottom);
}
+ paint.setHyphenEdit(0);
}
TextLine.recycle(tl);
diff --git a/core/java/android/text/method/AllCapsTransformationMethod.java b/core/java/android/text/method/AllCapsTransformationMethod.java
index f9920dd..0cea821 100644
--- a/core/java/android/text/method/AllCapsTransformationMethod.java
+++ b/core/java/android/text/method/AllCapsTransformationMethod.java
@@ -19,6 +19,7 @@
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
+import android.widget.TextView;
import java.util.Locale;
@@ -39,11 +40,23 @@
@Override
public CharSequence getTransformation(CharSequence source, View view) {
- if (mEnabled) {
- return source != null ? source.toString().toUpperCase(mLocale) : null;
+ if (!mEnabled) {
+ Log.w(TAG, "Caller did not enable length changes; not transforming text");
+ return source;
}
- Log.w(TAG, "Caller did not enable length changes; not transforming text");
- return source;
+
+ if (source == null) {
+ return null;
+ }
+
+ Locale locale = null;
+ if (view instanceof TextView) {
+ locale = ((TextView)view).getTextLocale();
+ }
+ if (locale == null) {
+ locale = mLocale;
+ }
+ return source.toString().toUpperCase(locale);
}
@Override
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index 63607fa..07c1ec3 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -22,6 +22,8 @@
import android.text.method.TextKeyListener.Capitalize;
import android.widget.TextView;
+import java.text.BreakIterator;
+
/**
* Abstract base class for key listeners.
*
@@ -63,9 +65,9 @@
private boolean backspaceOrForwardDelete(View view, Editable content, int keyCode,
KeyEvent event, boolean isForwardDelete) {
- // Ensure the key event does not have modifiers except ALT or SHIFT.
+ // Ensure the key event does not have modifiers except ALT or SHIFT or CTRL.
if (!KeyEvent.metaStateHasNoModifiers(event.getMetaState()
- & ~(KeyEvent.META_SHIFT_MASK | KeyEvent.META_ALT_MASK))) {
+ & ~(KeyEvent.META_SHIFT_MASK | KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK))) {
return false;
}
@@ -74,18 +76,28 @@
return true;
}
- // Alt+Backspace or Alt+ForwardDelete deletes the current line, if possible.
- if (getMetaState(content, META_ALT_ON, event) == 1) {
- if (deleteLine(view, content)) {
- return true;
+ // MetaKeyKeyListener doesn't track control key state. Need to check the KeyEvent instead.
+ boolean isCtrlActive = ((event.getMetaState() & KeyEvent.META_CTRL_ON) != 0);
+ boolean isShiftActive = (getMetaState(content, META_SHIFT_ON, event) == 1);
+ boolean isAltActive = (getMetaState(content, META_ALT_ON, event) == 1);
+
+ if (isCtrlActive) {
+ if (isAltActive || isShiftActive) {
+ // Ctrl+Alt, Ctrl+Shift, Ctrl+Alt+Shift should not delete any characters.
+ return false;
}
+ return deleteUntilWordBoundary(view, content, isForwardDelete);
+ }
+
+ // Alt+Backspace or Alt+ForwardDelete deletes the current line, if possible.
+ if (isAltActive && deleteLine(view, content)) {
+ return true;
}
// Delete a character.
final int start = Selection.getSelectionEnd(content);
final int end;
- if (isForwardDelete || event.isShiftPressed()
- || getMetaState(content, META_SHIFT_ON) == 1) {
+ if (isForwardDelete || event.isShiftPressed() || isShiftActive) {
end = TextUtils.getOffsetAfter(content, start);
} else {
end = TextUtils.getOffsetBefore(content, start);
@@ -97,6 +109,54 @@
return false;
}
+ private boolean deleteUntilWordBoundary(View view, Editable content, boolean isForwardDelete) {
+ int currentCursorOffset = Selection.getSelectionStart(content);
+
+ // If there is a selection, do nothing.
+ if (currentCursorOffset != Selection.getSelectionEnd(content)) {
+ return false;
+ }
+
+ // Early exit if there is no contents to delete.
+ if ((!isForwardDelete && currentCursorOffset == 0) ||
+ (isForwardDelete && currentCursorOffset == content.length())) {
+ return false;
+ }
+
+ WordIterator wordIterator = null;
+ if (view instanceof TextView) {
+ wordIterator = ((TextView)view).getWordIterator();
+ }
+
+ if (wordIterator == null) {
+ // Default locale is used for WordIterator since the appropriate locale is not clear
+ // here.
+ // TODO: Use appropriate locale for WordIterator.
+ wordIterator = new WordIterator();
+ }
+
+ int deleteFrom;
+ int deleteTo;
+
+ if (isForwardDelete) {
+ deleteFrom = currentCursorOffset;
+ wordIterator.setCharSequence(content, deleteFrom, content.length());
+ deleteTo = wordIterator.following(currentCursorOffset);
+ if (deleteTo == BreakIterator.DONE) {
+ deleteTo = content.length();
+ }
+ } else {
+ deleteTo = currentCursorOffset;
+ wordIterator.setCharSequence(content, 0, deleteTo);
+ deleteFrom = wordIterator.preceding(currentCursorOffset);
+ if (deleteFrom == BreakIterator.DONE) {
+ deleteFrom = 0;
+ }
+ }
+ content.delete(deleteFrom, deleteTo);
+ return true;
+ }
+
private boolean deleteSelection(View view, Editable content) {
int selectionStart = Selection.getSelectionStart(content);
int selectionEnd = Selection.getSelectionEnd(content);
diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java
index 0b70fdb..5209f90 100644
--- a/core/java/android/transition/TransitionManager.java
+++ b/core/java/android/transition/TransitionManager.java
@@ -430,7 +430,6 @@
* Ends all pending and ongoing transitions on the specified scene root.
*
* @param sceneRoot The root of the View hierarchy to end transitions on.
- * @hide
*/
public static void endTransitions(final ViewGroup sceneRoot) {
sPendingTransitions.remove(sceneRoot);
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 2eac549..1ee47802 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -49,7 +49,6 @@
private final String mName;
private final int mVendorId;
private final int mProductId;
- private final String mUniqueId;
private final String mDescriptor;
private final InputDeviceIdentifier mIdentifier;
private final boolean mIsExternal;
@@ -57,6 +56,7 @@
private final int mKeyboardType;
private final KeyCharacterMap mKeyCharacterMap;
private final boolean mHasVibrator;
+ private final boolean mHasMic;
private final boolean mHasButtonUnderPad;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
@@ -357,8 +357,8 @@
// Called by native code.
private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
- int productId, String uniqueId, String descriptor, boolean isExternal, int sources,
- int keyboardType, KeyCharacterMap keyCharacterMap, boolean hasVibrator,
+ int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
+ KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasMic,
boolean hasButtonUnderPad) {
mId = id;
mGeneration = generation;
@@ -366,13 +366,13 @@
mName = name;
mVendorId = vendorId;
mProductId = productId;
- mUniqueId = uniqueId;
mDescriptor = descriptor;
mIsExternal = isExternal;
mSources = sources;
mKeyboardType = keyboardType;
mKeyCharacterMap = keyCharacterMap;
mHasVibrator = hasVibrator;
+ mHasMic = hasMic;
mHasButtonUnderPad = hasButtonUnderPad;
mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId);
}
@@ -384,13 +384,13 @@
mName = in.readString();
mVendorId = in.readInt();
mProductId = in.readInt();
- mUniqueId = in.readString();
mDescriptor = in.readString();
mIsExternal = in.readInt() != 0;
mSources = in.readInt();
mKeyboardType = in.readInt();
mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in);
mHasVibrator = in.readInt() != 0;
+ mHasMic = in.readInt() != 0;
mHasButtonUnderPad = in.readInt() != 0;
mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId);
@@ -509,23 +509,6 @@
}
/**
- * Gets the vendor's unique id for the given device, if available.
- * <p>
- * A vendor may assign a unique id to a device (e.g., MAC address for
- * Bluetooth devices). A null value will be assigned where a unique id is
- * not available.
- * </p><p>
- * This method is dependent on the vendor, whereas {@link #getDescriptor}
- * attempts to create a unique id even when the vendor has not provided one.
- * </p>
- *
- * @return The unique id of a given device
- */
- public String getUniqueId() {
- return mUniqueId;
- }
-
- /**
* Gets the input device descriptor, which is a stable identifier for an input device.
* <p>
* An input device descriptor uniquely identifies an input device. Its value
@@ -737,6 +720,14 @@
}
/**
+ * Reports whether the device has a built-in microphone.
+ * @return Whether the device has a built-in microphone.
+ */
+ public boolean hasMic() {
+ return mHasMic;
+ }
+
+ /**
* Reports whether the device has a button under its touchpad
* @return Whether the device has a button under its touchpad
* @hide
@@ -864,13 +855,13 @@
out.writeString(mName);
out.writeInt(mVendorId);
out.writeInt(mProductId);
- out.writeString(mUniqueId);
out.writeString(mDescriptor);
out.writeInt(mIsExternal ? 1 : 0);
out.writeInt(mSources);
out.writeInt(mKeyboardType);
mKeyCharacterMap.writeToParcel(out, flags);
out.writeInt(mHasVibrator ? 1 : 0);
+ out.writeInt(mHasMic ? 1 : 0);
out.writeInt(mHasButtonUnderPad ? 1 : 0);
final int numRanges = mMotionRanges.size();
@@ -916,6 +907,8 @@
description.append(" Has Vibrator: ").append(mHasVibrator).append("\n");
+ description.append(" Has mic: ").append(mHasMic).append("\n");
+
description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 36bce0b..f951dc2 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -397,7 +397,7 @@
public void updateMenuView(boolean cleared) {
final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
if (menuViewParent != null) {
- setupItemAnimations();
+// setupItemAnimations();
ActionBarTransition.beginDelayedTransition(menuViewParent);
}
super.updateMenuView(cleared);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 32b99a8..491826a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -557,7 +557,7 @@
}
}
- private void hideInsertionPointCursorController() {
+ void hideInsertionPointCursorController() {
if (mInsertionPointCursorController != null) {
mInsertionPointCursorController.hide();
}
@@ -1668,10 +1668,12 @@
if (mSelectionActionMode != null) {
// Selection action mode is already started
// TODO: revisit invocations to minimize this case.
+ mSelectionActionMode.invalidate();
return false;
}
ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
- mSelectionActionMode = mTextView.startActionMode(actionModeCallback);
+ mSelectionActionMode = mTextView.startActionMode(
+ actionModeCallback, ActionMode.TYPE_FLOATING);
return mSelectionActionMode != null;
}
@@ -1681,6 +1683,7 @@
boolean startSelectionActionModeWithSelection() {
if (mSelectionActionMode != null) {
// Selection action mode is already started
+ mSelectionActionMode.invalidate();
return false;
}
@@ -1704,7 +1707,8 @@
// immediately hide the newly created action bar and would be visually distracting.
if (!willExtract) {
ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
- mSelectionActionMode = mTextView.startActionMode(actionModeCallback);
+ mSelectionActionMode = mTextView.startActionMode(
+ actionModeCallback, ActionMode.TYPE_FLOATING);
}
final boolean selectionStarted = mSelectionActionMode != null || willExtract;
@@ -2963,6 +2967,28 @@
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ mode.setTitle(mTextView.getContext().getString(
+ com.android.internal.R.string.textSelectionCABTitle));
+ mode.setSubtitle(null);
+ mode.setTitleOptionalHint(true);
+ populateMenuWithItems(menu);
+
+ if (mCustomSelectionActionModeCallback != null) {
+ if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
+ // The custom mode can choose to cancel the action mode
+ return false;
+ }
+ }
+
+ if (menu.hasVisibleItems() || mode.getCustomView() != null) {
+ mTextView.setHasTransientState(true);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void populateMenuWithItems(Menu menu) {
final boolean legacy = mTextView.getContext().getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.LOLLIPOP;
final Context context = !legacy && menu instanceof MenuBuilder ?
@@ -2971,11 +2997,6 @@
final TypedArray styledAttributes = context.obtainStyledAttributes(
com.android.internal.R.styleable.SelectionModeDrawables);
- mode.setTitle(mTextView.getContext().getString(
- com.android.internal.R.string.textSelectionCABTitle));
- mode.setSubtitle(null);
- mode.setTitleOptionalHint(true);
-
if (mTextView.canCut()) {
menu.add(0, TextView.ID_CUT, 0, com.android.internal.R.string.cut).
setIcon(styledAttributes.getResourceId(
@@ -3010,37 +3031,33 @@
setShowAsAction(
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
- if (mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan()) {
- menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace).
- setShowAsAction(
- MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
- }
+ updateReplaceItem(menu);
styledAttributes.recycle();
-
- if (mCustomSelectionActionModeCallback != null) {
- if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
- // The custom mode can choose to cancel the action mode
- return false;
- }
- }
-
- if (menu.hasVisibleItems() || mode.getCustomView() != null) {
- mTextView.setHasTransientState(true);
- return true;
- } else {
- return false;
- }
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ updateReplaceItem(menu);
+
if (mCustomSelectionActionModeCallback != null) {
return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
}
return true;
}
+ private void updateReplaceItem(Menu menu) {
+ boolean canReplace = mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan();
+ boolean replaceItemExists = menu.findItem(TextView.ID_REPLACE) != null;
+ if (canReplace && !replaceItemExists) {
+ menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace).
+ setShowAsAction(
+ MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ } else if (!canReplace && replaceItemExists) {
+ menu.removeItem(TextView.ID_REPLACE);
+ }
+ }
+
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (mCustomSelectionActionModeCallback != null &&
@@ -3091,6 +3108,17 @@
mTextView.getSelectionStart(), mTextView.getSelectionEnd(), mSelectionPath);
mSelectionPath.computeBounds(mSelectionBounds, true);
mSelectionBounds.bottom += mSelectionHandleHeight;
+ } else if (mCursorCount == 2) {
+ // We have a split cursor. In this case, we take the rectangle that includes both
+ // parts of the cursor to ensure we don't obscure either of them.
+ Rect firstCursorBounds = mCursorDrawable[0].getBounds();
+ Rect secondCursorBounds = mCursorDrawable[1].getBounds();
+ mSelectionBounds.set(
+ Math.min(firstCursorBounds.left, secondCursorBounds.left),
+ Math.min(firstCursorBounds.top, secondCursorBounds.top),
+ Math.max(firstCursorBounds.right, secondCursorBounds.right),
+ Math.max(firstCursorBounds.bottom, secondCursorBounds.bottom)
+ + mInsertionHandleHeight);
} else {
// We have a single cursor.
int line = mTextView.getLayout().getLineForOffset(mTextView.getSelectionStart());
@@ -3796,6 +3824,9 @@
Selection.setSelection((Spannable) mTextView.getText(), offset,
mTextView.getSelectionEnd());
updateDrawable();
+ if (mSelectionActionMode != null) {
+ mSelectionActionMode.invalidate();
+ }
}
@Override
@@ -3898,6 +3929,9 @@
public void updateSelection(int offset) {
Selection.setSelection((Spannable) mTextView.getText(),
mTextView.getSelectionStart(), offset);
+ if (mSelectionActionMode != null) {
+ mSelectionActionMode.invalidate();
+ }
updateDrawable();
}
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index 21213ac..4b5407a 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -46,6 +46,8 @@
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView.ScaleType;
+import com.android.internal.R;
+
/**
* Helper class for AbsListView to draw and control the Fast Scroll thumb
*/
@@ -82,6 +84,10 @@
private static final int OVERLAY_AT_THUMB = 1;
private static final int OVERLAY_ABOVE_THUMB = 2;
+ // Positions for thumb in relation to track.
+ private static final int THUMB_POSITION_MIDPOINT = 0;
+ private static final int THUMB_POSITION_INSIDE = 1;
+
// Indices for mPreviewResId.
private static final int PREVIEW_LEFT = 0;
private static final int PREVIEW_RIGHT = 1;
@@ -100,7 +106,6 @@
private final ImageView mThumbImage;
private final ImageView mTrackImage;
private final View mPreviewImage;
-
/**
* Preview image resource IDs for left- and right-aligned layouts. See
* {@link #PREVIEW_LEFT} and {@link #PREVIEW_RIGHT}.
@@ -130,6 +135,11 @@
private Drawable mThumbDrawable;
private Drawable mTrackDrawable;
private int mTextAppearance;
+ private int mThumbPosition;
+
+ // Used to convert between y-coordinate and thumb position within track.
+ private float mThumbOffset;
+ private float mThumbRange;
/** Total width of decorations. */
private int mWidth;
@@ -278,7 +288,6 @@
}
private void updateAppearance() {
- final Context context = mList.getContext();
int width = 0;
// Add track to overlay if it has an image.
@@ -298,12 +307,9 @@
// Account for minimum thumb width.
mWidth = Math.max(width, mThumbMinWidth);
- mPreviewImage.setMinimumWidth(mPreviewMinWidth);
- mPreviewImage.setMinimumHeight(mPreviewMinHeight);
-
if (mTextAppearance != 0) {
- mPrimaryText.setTextAppearance(context, mTextAppearance);
- mSecondaryText.setTextAppearance(context, mTextAppearance);
+ mPrimaryText.setTextAppearance(mTextAppearance);
+ mSecondaryText.setTextAppearance(mTextAppearance);
}
if (mTextColor != null) {
@@ -316,13 +322,11 @@
mSecondaryText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
}
- final int textMinSize = Math.max(0, mPreviewMinHeight);
- mPrimaryText.setMinimumWidth(textMinSize);
- mPrimaryText.setMinimumHeight(textMinSize);
+ final int padding = mPreviewPadding;
mPrimaryText.setIncludeFontPadding(false);
- mSecondaryText.setMinimumWidth(textMinSize);
- mSecondaryText.setMinimumHeight(textMinSize);
+ mPrimaryText.setPadding(padding, padding, padding, padding);
mSecondaryText.setIncludeFontPadding(false);
+ mSecondaryText.setPadding(padding, padding, padding, padding);
refreshDrawablePressedState();
}
@@ -330,50 +334,53 @@
public void setStyle(@StyleRes int resId) {
final Context context = mList.getContext();
final TypedArray ta = context.obtainStyledAttributes(null,
- com.android.internal.R.styleable.FastScroll, android.R.attr.fastScrollStyle, resId);
+ R.styleable.FastScroll, R.attr.fastScrollStyle, resId);
final int N = ta.getIndexCount();
for (int i = 0; i < N; i++) {
final int index = ta.getIndex(i);
switch (index) {
- case com.android.internal.R.styleable.FastScroll_position:
+ case R.styleable.FastScroll_position:
mOverlayPosition = ta.getInt(index, OVERLAY_FLOATING);
break;
- case com.android.internal.R.styleable.FastScroll_backgroundLeft:
+ case R.styleable.FastScroll_backgroundLeft:
mPreviewResId[PREVIEW_LEFT] = ta.getResourceId(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_backgroundRight:
+ case R.styleable.FastScroll_backgroundRight:
mPreviewResId[PREVIEW_RIGHT] = ta.getResourceId(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_thumbDrawable:
+ case R.styleable.FastScroll_thumbDrawable:
mThumbDrawable = ta.getDrawable(index);
break;
- case com.android.internal.R.styleable.FastScroll_trackDrawable:
+ case R.styleable.FastScroll_trackDrawable:
mTrackDrawable = ta.getDrawable(index);
break;
- case com.android.internal.R.styleable.FastScroll_textAppearance:
+ case R.styleable.FastScroll_textAppearance:
mTextAppearance = ta.getResourceId(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_textColor:
+ case R.styleable.FastScroll_textColor:
mTextColor = ta.getColorStateList(index);
break;
- case com.android.internal.R.styleable.FastScroll_textSize:
+ case R.styleable.FastScroll_textSize:
mTextSize = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_minWidth:
+ case R.styleable.FastScroll_minWidth:
mPreviewMinWidth = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_minHeight:
+ case R.styleable.FastScroll_minHeight:
mPreviewMinHeight = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_thumbMinWidth:
+ case R.styleable.FastScroll_thumbMinWidth:
mThumbMinWidth = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_thumbMinHeight:
+ case R.styleable.FastScroll_thumbMinHeight:
mThumbMinHeight = ta.getDimensionPixelSize(index, 0);
break;
- case com.android.internal.R.styleable.FastScroll_padding:
+ case R.styleable.FastScroll_padding:
mPreviewPadding = ta.getDimensionPixelSize(index, 0);
break;
+ case R.styleable.FastScroll_thumbPosition:
+ mThumbPosition = ta.getInt(index, THUMB_POSITION_MIDPOINT);
+ break;
}
}
@@ -478,14 +485,16 @@
final int previewResId = mPreviewResId[mLayoutFromRight ? PREVIEW_RIGHT : PREVIEW_LEFT];
mPreviewImage.setBackgroundResource(previewResId);
- // Add extra padding for text.
- final Drawable background = mPreviewImage.getBackground();
- if (background != null) {
- final Rect padding = mTempBounds;
- background.getPadding(padding);
- padding.offset(mPreviewPadding, mPreviewPadding);
- mPreviewImage.setPadding(padding.left, padding.top, padding.right, padding.bottom);
- }
+ // Propagate padding to text min width/height.
+ final int textMinWidth = Math.max(0, mPreviewMinWidth - mPreviewImage.getPaddingLeft()
+ - mPreviewImage.getPaddingRight());
+ mPrimaryText.setMinimumWidth(textMinWidth);
+ mSecondaryText.setMinimumWidth(textMinWidth);
+
+ final int textMinHeight = Math.max(0, mPreviewMinHeight - mPreviewImage.getPaddingTop()
+ - mPreviewImage.getPaddingBottom());
+ mPrimaryText.setMinimumHeight(textMinHeight);
+ mSecondaryText.setMinimumHeight(textMinHeight);
// Requires re-layout.
updateLayout();
@@ -560,6 +569,8 @@
layoutThumb();
layoutTrack();
+ updateOffsetAndRange();
+
final Rect bounds = mTempBounds;
measurePreview(mPrimaryText, bounds);
applyLayout(mPrimaryText, bounds);
@@ -758,15 +769,45 @@
final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
track.measure(widthMeasureSpec, heightMeasureSpec);
+ final int top;
+ final int bottom;
+ if (mThumbPosition == THUMB_POSITION_INSIDE) {
+ top = container.top;
+ bottom = container.bottom;
+ } else {
+ final int thumbHalfHeight = thumb.getHeight() / 2;
+ top = container.top + thumbHalfHeight;
+ bottom = container.bottom - thumbHalfHeight;
+ }
+
final int trackWidth = track.getMeasuredWidth();
- final int thumbHalfHeight = thumb.getHeight() / 2;
final int left = thumb.getLeft() + (thumb.getWidth() - trackWidth) / 2;
final int right = left + trackWidth;
- final int top = container.top + thumbHalfHeight;
- final int bottom = container.bottom - thumbHalfHeight;
track.layout(left, top, right, bottom);
}
+ /**
+ * Updates the offset and range used to convert from absolute y-position to
+ * thumb position within the track.
+ */
+ private void updateOffsetAndRange() {
+ final View trackImage = mTrackImage;
+ final View thumbImage = mThumbImage;
+ final float min;
+ final float max;
+ if (mThumbPosition == THUMB_POSITION_INSIDE) {
+ final float halfThumbHeight = thumbImage.getHeight() / 2f;
+ min = trackImage.getTop() + halfThumbHeight;
+ max = trackImage.getBottom() - halfThumbHeight;
+ } else{
+ min = trackImage.getTop();
+ max = trackImage.getBottom();
+ }
+
+ mThumbOffset = min;
+ mThumbRange = max - min;
+ }
+
private void setState(int state) {
mList.removeCallbacks(mDeferHide);
@@ -1145,18 +1186,8 @@
* to place the thumb.
*/
private void setThumbPos(float position) {
- final Rect container = mContainerRect;
- final int top = container.top;
- final int bottom = container.bottom;
-
- final View trackImage = mTrackImage;
- final View thumbImage = mThumbImage;
- final float min = trackImage.getTop();
- final float max = trackImage.getBottom();
- final float offset = min;
- final float range = max - min;
- final float thumbMiddle = position * range + offset;
- thumbImage.setTranslationY(thumbMiddle - thumbImage.getHeight() / 2);
+ final float thumbMiddle = position * mThumbRange + mThumbOffset;
+ mThumbImage.setTranslationY(thumbMiddle - mThumbImage.getHeight() / 2f);
final View previewImage = mPreviewImage;
final float previewHalfHeight = previewImage.getHeight() / 2f;
@@ -1175,6 +1206,9 @@
}
// Center the preview on the thumb, constrained to the list bounds.
+ final Rect container = mContainerRect;
+ final int top = container.top;
+ final int bottom = container.bottom;
final float minP = top + previewHalfHeight;
final float maxP = bottom - previewHalfHeight;
final float previewMiddle = MathUtils.constrain(previewPos, minP, maxP);
@@ -1186,19 +1220,13 @@
}
private float getPosFromMotionEvent(float y) {
- final View trackImage = mTrackImage;
- final float min = trackImage.getTop();
- final float max = trackImage.getBottom();
- final float offset = min;
- final float range = max - min;
-
// If the list is the same height as the thumbnail or shorter,
// effectively disable scrolling.
- if (range <= 0) {
+ if (mThumbRange <= 0) {
return 0f;
}
- return MathUtils.constrain((y - offset) / range, 0f, 1f);
+ return MathUtils.constrain((y - mThumbOffset) / mThumbRange, 0f, 1f);
}
/**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9caa584..11439e4 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9067,6 +9067,12 @@
}
boolean selectAllText() {
+ // Need to hide insert point cursor controller before settings selection, otherwise insert
+ // point cursor controller obtains cursor update event and update cursor with cancelling
+ // selection.
+ if (mEditor != null) {
+ mEditor.hideInsertionPointCursorController();
+ }
final int length = mText.length();
Selection.setSelection((Spannable) mText, 0, length);
return length > 0;
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 1746bed..4f0e29e 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -40,6 +40,9 @@
ParcelFileDescriptor getStatisticsStream();
+ // Return true if we see the battery as currently charging.
+ boolean isCharging();
+
// Return the computed amount of time remaining on battery, in milliseconds.
// Returns -1 if nothing could be computed.
long computeBatteryTimeRemaining();
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 75beee9..fe79eff 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -140,6 +140,8 @@
STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT
STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
STATE_TOP, // ActivityManager.PROCESS_STATE_TOP
+ STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ STATE_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
STATE_IMPORTANT_FOREGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
STATE_IMPORTANT_BACKGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
STATE_BACKUP, // ActivityManager.PROCESS_STATE_BACKUP
diff --git a/core/java/com/android/internal/logging/EventLogTags.logtags b/core/java/com/android/internal/logging/EventLogTags.logtags
index 870d20d..b9208ff 100644
--- a/core/java/com/android/internal/logging/EventLogTags.logtags
+++ b/core/java/com/android/internal/logging/EventLogTags.logtags
@@ -5,3 +5,5 @@
# interaction logs
524287 sysui_view_visibility (category|1|5),(visible|1|6)
524288 sysui_action (category|1|5)
+524290 sysui_count (name|3),(increment|1)
+524291 sysui_histogram (name|3),(bucket|1)
diff --git a/core/java/com/android/internal/logging/MetricsConstants.java b/core/java/com/android/internal/logging/MetricsConstants.java
index e5cba84..ee225a1 100644
--- a/core/java/com/android/internal/logging/MetricsConstants.java
+++ b/core/java/com/android/internal/logging/MetricsConstants.java
@@ -32,9 +32,16 @@
public static final int ACCOUNTS_ACCOUNT_SYNC = 9;
public static final int ACCOUNTS_CHOOSE_ACCOUNT_ACTIVITY = 10;
public static final int ACCOUNTS_MANAGE_ACCOUNTS = 11;
+ public static final int ACTION_WIFI_ADD_NETWORK = 134;
+ public static final int ACTION_WIFI_CONNECT = 135;
+ public static final int ACTION_WIFI_FORCE_SCAN = 136;
+ public static final int ACTION_WIFI_FORGET = 137;
+ public static final int ACTION_WIFI_OFF = 138;
+ public static final int ACTION_WIFI_ON = 139;
public static final int APN = 12;
public static final int APN_EDITOR = 13;
public static final int APPLICATION = 16;
+ public static final int APPLICATIONS_ADVANCED = 130;
public static final int APPLICATIONS_APP_LAUNCH = 17;
public static final int APPLICATIONS_APP_PERMISSION = 18;
public static final int APPLICATIONS_APP_STORAGE = 19;
@@ -85,8 +92,13 @@
public static final int INPUTMETHOD_USER_DICTIONARY_ADD_WORD = 62;
public static final int LOCATION = 63;
public static final int LOCATION_MODE = 64;
+ public static final int LOCATION_SCANNING = 131;
public static final int MAIN_SETTINGS = 1;
public static final int MANAGE_APPLICATIONS = 65;
+ public static final int MANAGE_APPLICATIONS_ALL = 132;
+ public static final int MANAGE_APPLICATIONS_NOTIFICATIONS = 133;
+ public static final int MANAGE_DOMAIN_URLS = 143;
+ public static final int MANAGE_PERMISSIONS = 140;
public static final int MASTER_CLEAR = 66;
public static final int MASTER_CLEAR_CONFIRM = 67;
public static final int NET_DATA_USAGE_METERED = 68;
@@ -94,10 +106,15 @@
public static final int NFC_PAYMENT = 70;
public static final int NOTIFICATION = 71;
public static final int NOTIFICATION_APP_NOTIFICATION = 72;
+ public static final int NOTIFICATION_ITEM = 128;
+ public static final int NOTIFICATION_ITEM_ACTION = 129;
public static final int NOTIFICATION_OTHER_SOUND = 73;
+ public static final int NOTIFICATION_PANEL = 127;
public static final int NOTIFICATION_REDACTION = 74;
public static final int NOTIFICATION_STATION = 75;
public static final int NOTIFICATION_ZEN_MODE = 76;
+ public static final int NOTIFICATION_ZEN_MODE_AUTOMATION = 142;
+ public static final int NOTIFICATION_ZEN_MODE_PRIORITY = 141;
public static final int OWNER_INFO = 77;
public static final int PRINT_JOB_SETTINGS = 78;
public static final int PRINT_SERVICE_SETTINGS = 79;
@@ -132,7 +149,6 @@
public static final int TRUST_AGENT = 91;
public static final int TTS_ENGINE_SETTINGS = 93;
public static final int TTS_TEXT_TO_SPEECH = 94;
- public static final int TYPE_UNKNOWN = 0;
public static final int USAGE_ACCESS = 95;
public static final int USER = 96;
public static final int USERS_APP_RESTRICTIONS = 97;
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 1038543..6be6389 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -26,23 +26,9 @@
*/
public class MetricsLogger implements MetricsConstants {
// These constants are temporary, they should migrate to MetricsConstants.
- public static final int APPLICATIONS_ADVANCED = 132;
- public static final int LOCATION_SCANNING = 133;
- public static final int MANAGE_APPLICATIONS_ALL = 134;
- public static final int MANAGE_APPLICATIONS_NOTIFICATIONS = 135;
+ // next value is 145;
- public static final int ACTION_WIFI_ADD_NETWORK = 136;
- public static final int ACTION_WIFI_CONNECT = 137;
- public static final int ACTION_WIFI_FORCE_SCAN = 138;
- public static final int ACTION_WIFI_FORGET = 139;
- public static final int ACTION_WIFI_OFF = 140;
- public static final int ACTION_WIFI_ON = 141;
-
- public static final int MANAGE_PERMISSIONS = 142;
- public static final int NOTIFICATION_ZEN_MODE_PRIORITY = 143;
- public static final int NOTIFICATION_ZEN_MODE_AUTOMATION = 144;
-
- public static final int MANAGE_DOMAIN_URLS = 143;
+ public static final int NOTIFICATION_ZEN_MODE_SCHEDULE_RULE = 144;
public static void visible(Context context, int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
@@ -64,4 +50,14 @@
}
EventLogTags.writeSysuiAction(category);
}
+
+ /** Add an integer value to the monotonically increasing counter with the given name. */
+ public static void count(Context context, String name, int value) {
+ EventLogTags.writeSysuiCount(name, value);
+ }
+
+ /** Increment the bucket with the integer label on the histogram with the given name. */
+ public static void histogram(Context context, String name, int bucket) {
+ EventLogTags.writeSysuiHistogram(name, bucket);
+ }
}
diff --git a/core/java/com/android/internal/midi/EventScheduler.java b/core/java/com/android/internal/midi/EventScheduler.java
index 7b9a48c..506902f6 100644
--- a/core/java/com/android/internal/midi/EventScheduler.java
+++ b/core/java/com/android/internal/midi/EventScheduler.java
@@ -16,6 +16,7 @@
package com.android.internal.midi;
+import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;
@@ -28,7 +29,7 @@
private static final long NANOS_PER_MILLI = 1000000;
private final Object mLock = new Object();
- private SortedMap<Long, FastEventQueue> mEventBuffer;
+ volatile private SortedMap<Long, FastEventQueue> mEventBuffer;
private FastEventQueue mEventPool = null;
private int mMaxPoolSize = 200;
private boolean mClosed;
@@ -68,6 +69,7 @@
mEventsRemoved++;
SchedulableEvent event = mFirst;
mFirst = event.mNext;
+ event.mNext = null;
return event;
}
@@ -87,7 +89,7 @@
*/
public static class SchedulableEvent {
private long mTimestamp;
- private SchedulableEvent mNext = null;
+ volatile private SchedulableEvent mNext = null;
/**
* @param timestamp
@@ -235,6 +237,11 @@
return event;
}
+ protected void flush() {
+ // Replace our event buffer with a fresh empty one
+ mEventBuffer = new TreeMap<Long, FastEventQueue>();
+ }
+
public void close() {
synchronized (mLock) {
mClosed = true;
diff --git a/core/java/com/android/internal/midi/MidiConstants.java b/core/java/com/android/internal/midi/MidiConstants.java
index 87552e4..f78f75a 100644
--- a/core/java/com/android/internal/midi/MidiConstants.java
+++ b/core/java/com/android/internal/midi/MidiConstants.java
@@ -19,7 +19,7 @@
/**
* MIDI related constants and static methods.
*/
-public class MidiConstants {
+public final class MidiConstants {
public static final byte STATUS_COMMAND_MASK = (byte) 0xF0;
public static final byte STATUS_CHANNEL_MASK = (byte) 0x0F;
@@ -85,4 +85,16 @@
}
return (goodBytes == 0);
}
+
+ // Returns true if this command can be used for running status
+ public static boolean allowRunningStatus(int command) {
+ // only Channel Voice and Channel Mode commands can use running status
+ return (command >= STATUS_NOTE_OFF && command < STATUS_SYSTEM_EXCLUSIVE);
+ }
+
+ // Returns true if this command cancels running status
+ public static boolean cancelsRunningStatus(int command) {
+ // System Common messages cancel running status
+ return (command >= STATUS_SYSTEM_EXCLUSIVE && command <= STATUS_END_SYSEX);
+ }
}
diff --git a/core/java/com/android/internal/midi/MidiDispatcher.java b/core/java/com/android/internal/midi/MidiDispatcher.java
index 377bc68..70e699a 100644
--- a/core/java/com/android/internal/midi/MidiDispatcher.java
+++ b/core/java/com/android/internal/midi/MidiDispatcher.java
@@ -83,4 +83,11 @@
}
}
}
+
+ @Override
+ public void flush() throws IOException {
+ for (MidiReceiver receiver : mReceivers) {
+ receiver.flush();
+ }
+ }
}
diff --git a/core/java/com/android/internal/midi/MidiEventScheduler.java b/core/java/com/android/internal/midi/MidiEventScheduler.java
index 42d70f6..4dc5838 100644
--- a/core/java/com/android/internal/midi/MidiEventScheduler.java
+++ b/core/java/com/android/internal/midi/MidiEventScheduler.java
@@ -28,16 +28,9 @@
// Maintain a pool of scheduled events to reduce memory allocation.
// This pool increases performance by about 14%.
private final static int POOL_EVENT_SIZE = 16;
-
- private final MidiReceiver[] mReceivers;
+ private MidiReceiver mReceiver = new SchedulingReceiver();
private class SchedulingReceiver extends MidiReceiver {
- private final int mPortNumber;
-
- public SchedulingReceiver(int portNumber) {
- mPortNumber = portNumber;
- }
-
/**
* Store these bytes in the EventScheduler to be delivered at the specified
* time.
@@ -47,14 +40,17 @@
throws IOException {
MidiEvent event = createScheduledEvent(msg, offset, count, timestamp);
if (event != null) {
- event.portNumber = mPortNumber;
add(event);
}
}
+
+ @Override
+ public void flush() {
+ MidiEventScheduler.this.flush();
+ }
}
public static class MidiEvent extends SchedulableEvent {
- public int portNumber;
public int count = 0;
public byte[] data;
@@ -80,17 +76,6 @@
}
}
- public MidiEventScheduler() {
- this(0);
- }
-
- public MidiEventScheduler(int portCount) {
- mReceivers = new MidiReceiver[portCount];
- for (int i = 0; i < portCount; i++) {
- mReceivers[i] = new SchedulingReceiver(i);
- }
- }
-
/**
* Create an event that contains the message.
*/
@@ -132,15 +117,7 @@
* @return the MidiReceiver
*/
public MidiReceiver getReceiver() {
- return mReceivers[0];
- }
-
- /**
- * This MidiReceiver will write date to the scheduling buffer.
- * @return the MidiReceiver
- */
- public MidiReceiver getReceiver(int portNumber) {
- return mReceivers[portNumber];
+ return mReceiver;
}
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index 024b7c5..59dbec6 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -21,7 +21,6 @@
import android.content.IntentFilter;
import android.hardware.SensorManager;
import android.net.ConnectivityManager;
-import android.net.wifi.WifiManager;
import android.os.BatteryStats;
import android.os.BatteryStats.Uid;
import android.os.Bundle;
@@ -130,16 +129,11 @@
return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
}
- public static boolean checkHasWifiPowerReporting(Context context, PowerProfile profile) {
- WifiManager manager = context.getSystemService(WifiManager.class);
- if (manager.isEnhancedPowerReportingSupported()) {
- if (profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE) != 0 &&
- profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX) != 0 &&
- profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX) != 0) {
- return true;
- }
- }
- return false;
+ public static boolean checkHasWifiPowerReporting(BatteryStats stats, PowerProfile profile) {
+ return stats.hasWifiActivityReporting() &&
+ profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE) != 0 &&
+ profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX) != 0 &&
+ profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX) != 0;
}
public BatteryStatsHelper(Context context) {
@@ -339,7 +333,7 @@
mMobileRadioPowerCalculator.reset(mStats);
if (mWifiPowerCalculator == null) {
- if (checkHasWifiPowerReporting(mContext, mPowerProfile)) {
+ if (checkHasWifiPowerReporting(mStats, mPowerProfile)) {
mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile);
} else {
mWifiPowerCalculator = new WifiPowerEstimator(mPowerProfile);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 793d0d3..fbb2dfc 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -22,6 +22,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.content.Context;
+import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkStats;
import android.net.wifi.WifiActivityEnergyInfo;
@@ -127,6 +128,7 @@
static final int MSG_UPDATE_WAKELOCKS = 1;
static final int MSG_REPORT_POWER_CHANGE = 2;
+ static final int MSG_REPORT_CHARGING = 3;
static final long DELAY_UPDATE_WAKELOCKS = 5*1000;
private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
@@ -135,6 +137,7 @@
public interface BatteryCallback {
public void batteryNeedsCpuUpdate();
public void batteryPowerChanged(boolean onBattery);
+ public void batterySendBroadcast(Intent intent);
}
final class MyHandler extends Handler {
@@ -156,6 +159,18 @@
cb.batteryPowerChanged(msg.arg1 != 0);
}
break;
+ case MSG_REPORT_CHARGING:
+ if (cb != null) {
+ final String action;
+ synchronized (BatteryStatsImpl.this) {
+ action = mCharging ? BatteryManager.ACTION_CHARGING
+ : BatteryManager.ACTION_DISCHARGING;
+ }
+ Intent intent = new Intent(action);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ cb.batterySendBroadcast(intent);
+ }
+ break;
}
}
}
@@ -393,6 +408,12 @@
boolean mOnBattery;
boolean mOnBatteryInternal;
+ /**
+ * External reporting of whether the device is actually charging.
+ */
+ boolean mCharging = true;
+ int mLastChargingStateLevel;
+
/*
* These keep track of battery levels (1-100) at the last plug event and the last unplug event.
*/
@@ -451,6 +472,8 @@
private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry();
private PowerProfile mPowerProfile;
+ private boolean mHasWifiEnergyReporting = false;
+ private boolean mHasBluetoothEnergyReporting = false;
/*
* Holds a SamplingTimer associated with each kernel wakelock name being tracked.
@@ -4277,6 +4300,10 @@
return mBluetoothStateTimer[bluetoothState].getCountLocked(which);
}
+ @Override public boolean hasBluetoothActivityReporting() {
+ return mHasBluetoothEnergyReporting;
+ }
+
@Override public long getBluetoothControllerActivity(int type, int which) {
if (type >= 0 && type < mBluetoothActivityCounters.length) {
return mBluetoothActivityCounters[type].getCountLocked(which);
@@ -4284,6 +4311,10 @@
return 0;
}
+ @Override public boolean hasWifiActivityReporting() {
+ return mHasWifiEnergyReporting;
+ }
+
@Override public long getWifiControllerActivity(int type, int which) {
if (type >= 0 && type < mWifiActivityCounters.length) {
return mWifiActivityCounters[type].getCountLocked(which);
@@ -7243,6 +7274,10 @@
return mOnBattery;
}
+ public boolean isCharging() {
+ return mCharging;
+ }
+
public boolean isScreenOn() {
return mScreenState == Display.STATE_ON;
}
@@ -7542,6 +7577,8 @@
}
if (info != null) {
+ mHasWifiEnergyReporting = true;
+
// Measured in mAms
final long txTimeMs = info.getControllerTxTimeMillis();
final long rxTimeMs = info.getControllerRxTimeMillis();
@@ -7753,6 +7790,7 @@
*/
public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info) {
if (info != null && mOnBatteryInternal && false) {
+ mHasBluetoothEnergyReporting = true;
mBluetoothActivityCounters[CONTROLLER_RX_TIME].addCountLocked(
info.getControllerRxTimeMillis());
mBluetoothActivityCounters[CONTROLLER_TX_TIME].addCountLocked(
@@ -7802,6 +7840,20 @@
}
}
+ boolean setChargingLocked(boolean charging) {
+ if (mCharging != charging) {
+ mCharging = charging;
+ if (charging) {
+ mHistoryCur.states |= HistoryItem.STATE_CHARGING_FLAG;
+ } else {
+ mHistoryCur.states &= ~HistoryItem.STATE_CHARGING_FLAG;
+ }
+ mHandler.sendEmptyMessage(MSG_REPORT_CHARGING);
+ return true;
+ }
+ return false;
+ }
+
void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery,
final int oldStatus, final int level) {
boolean doWrite = false;
@@ -7861,6 +7913,10 @@
reset = true;
mDischargeStepTracker.init();
}
+ if (mCharging) {
+ setChargingLocked(false);
+ }
+ mLastChargingStateLevel = level;
mOnBattery = mOnBatteryInternal = true;
mLastDischargeStepLevel = level;
mMinDischargeStepLevel = level;
@@ -7890,6 +7946,7 @@
mDischargeAmountScreenOff = 0;
updateTimeBasesLocked(true, !screenOn, uptime, realtime);
} else {
+ mLastChargingStateLevel = level;
mOnBattery = mOnBatteryInternal = false;
pullPendingStateUpdatesLocked();
mHistoryCur.batteryLevel = (byte)level;
@@ -7982,10 +8039,13 @@
mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
}
}
+ // Always start out assuming charging, that will be updated later.
+ mHistoryCur.states |= HistoryItem.STATE_CHARGING_FLAG;
mHistoryCur.batteryStatus = (byte)status;
mHistoryCur.batteryLevel = (byte)level;
mMaxChargeStepLevel = mMinDischargeStepLevel =
mLastChargeStepLevel = mLastDischargeStepLevel = level;
+ mLastChargingStateLevel = level;
} else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) {
recordDailyStatsIfNeededLocked(level >= 100 && onBattery);
}
@@ -8046,13 +8106,11 @@
mHistoryCur.batteryVoltage = (char)volt;
changed = true;
}
- if (changed) {
- addHistoryRecordLocked(elapsedRealtime, uptime);
- }
long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
| (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
| (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
if (onBattery) {
+ changed |= setChargingLocked(false);
if (mLastDischargeStepLevel != level && mMinDischargeStepLevel > level) {
mDischargeStepTracker.addLevelSteps(mLastDischargeStepLevel - level,
modeBits, elapsedRealtime);
@@ -8064,6 +8122,28 @@
mModStepMode = 0;
}
} else {
+ if (level >= 90) {
+ // If the battery level is at least 90%, always consider the device to be
+ // charging even if it happens to go down a level.
+ changed |= setChargingLocked(true);
+ mLastChargeStepLevel = level;
+ } if (!mCharging) {
+ if (mLastChargeStepLevel < level) {
+ // We have not reporting that we are charging, but the level has now
+ // gone up, so consider the state to be charging.
+ changed |= setChargingLocked(true);
+ mLastChargeStepLevel = level;
+ }
+ } else {
+ if (mLastChargeStepLevel > level) {
+ // We had reported that the device was charging, but here we are with
+ // power connected and the level going down. Looks like the current
+ // power supplied isn't enough, so consider the device to now be
+ // discharging.
+ changed |= setChargingLocked(false);
+ mLastChargeStepLevel = level;
+ }
+ }
if (mLastChargeStepLevel != level && mMaxChargeStepLevel < level) {
mChargeStepTracker.addLevelSteps(level - mLastChargeStepLevel,
modeBits, elapsedRealtime);
@@ -8075,6 +8155,9 @@
mModStepMode = 0;
}
}
+ if (changed) {
+ addHistoryRecordLocked(elapsedRealtime, uptime);
+ }
}
if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) {
// We don't record history while we are plugged in and fully charged.
@@ -9463,6 +9546,8 @@
mWifiActivityCounters[i] = new LongSamplingCounter(mOnBatteryTimeBase, in);
}
+ mHasWifiEnergyReporting = in.readInt() != 0;
+ mHasBluetoothEnergyReporting = in.readInt() != 0;
mNumConnectivityChange = in.readInt();
mLoadedNumConnectivityChange = in.readInt();
mUnpluggedNumConnectivityChange = in.readInt();
@@ -9616,6 +9701,8 @@
for (int i=0; i< NUM_CONTROLLER_ACTIVITY_TYPES; i++) {
mWifiActivityCounters[i].writeToParcel(out);
}
+ out.writeInt(mHasWifiEnergyReporting ? 1 : 0);
+ out.writeInt(mHasBluetoothEnergyReporting ? 1 : 0);
out.writeInt(mNumConnectivityChange);
out.writeInt(mLoadedNumConnectivityChange);
out.writeInt(mUnpluggedNumConnectivityChange);
diff --git a/core/java/com/android/internal/os/InstallerConnection.java b/core/java/com/android/internal/os/InstallerConnection.java
index a4cdf19..671bf24 100644
--- a/core/java/com/android/internal/os/InstallerConnection.java
+++ b/core/java/com/android/internal/os/InstallerConnection.java
@@ -90,12 +90,15 @@
}
}
- public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) {
- return dexopt(apkPath, uid, isPublic, "*", instructionSet, false, false, null);
+ public int dexopt(String apkPath, int uid, boolean isPublic,
+ String instructionSet, int dexoptNeeded) {
+ return dexopt(apkPath, uid, isPublic, "*", instructionSet, dexoptNeeded,
+ false, false, null);
}
public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet, boolean vmSafeMode, boolean debuggable, String outputPath) {
+ String instructionSet, int dexoptNeeded, boolean vmSafeMode,
+ boolean debuggable, String outputPath) {
StringBuilder builder = new StringBuilder("dexopt");
builder.append(' ');
builder.append(apkPath);
@@ -106,6 +109,8 @@
builder.append(pkgName);
builder.append(' ');
builder.append(instructionSet);
+ builder.append(' ');
+ builder.append(dexoptNeeded);
builder.append(vmSafeMode ? " 1" : " 0");
builder.append(debuggable ? " 1" : " 0");
builder.append(' ');
@@ -113,25 +118,6 @@
return execute(builder.toString());
}
- public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) {
- return patchoat(apkPath, uid, isPublic, "*", instructionSet);
- }
-
- public int patchoat(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet) {
- StringBuilder builder = new StringBuilder("patchoat");
- builder.append(' ');
- builder.append(apkPath);
- builder.append(' ');
- builder.append(uid);
- builder.append(isPublic ? " 1" : " 0");
- builder.append(' ');
- builder.append(pkgName);
- builder.append(' ');
- builder.append(instructionSet);
- return execute(builder.toString());
- }
-
private boolean connect() {
if (mSocket != null) {
return true;
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
index 4e77f6b..4fb8b55 100644
--- a/core/java/com/android/internal/os/WifiPowerCalculator.java
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -16,12 +16,15 @@
package com.android.internal.os;
import android.os.BatteryStats;
+import android.util.Log;
/**
* WiFi power calculator for when BatteryStats supports energy reporting
* from the WiFi controller.
*/
public class WifiPowerCalculator extends PowerCalculator {
+ private static final boolean DEBUG = BatteryStatsHelper.DEBUG;
+ private static final String TAG = "WifiPowerCalculator";
private final double mIdleCurrentMa;
private final double mTxCurrentMa;
private final double mRxCurrentMa;
@@ -75,6 +78,10 @@
+ (rxTimeMs * mRxCurrentMa)) / (1000*60*60);
}
app.wifiPowerMah = Math.max(0, powerDrain - mTotalAppPowerDrain);
+
+ if (DEBUG) {
+ Log.d(TAG, "left over WiFi power: " + BatteryStatsHelper.makemAh(app.wifiPowerMah));
+ }
}
@Override
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 70f7b72..3ad4f1c 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -467,12 +467,11 @@
try {
for (String classPathElement : classPathElements) {
- final byte dexopt = DexFile.isDexOptNeededInternal(classPathElement, "*", instructionSet,
- false /* defer */);
- if (dexopt == DexFile.DEXOPT_NEEDED) {
- installer.dexopt(classPathElement, Process.SYSTEM_UID, false, instructionSet);
- } else if (dexopt == DexFile.PATCHOAT_NEEDED) {
- installer.patchoat(classPathElement, Process.SYSTEM_UID, false, instructionSet);
+ final int dexoptNeeded = DexFile.getDexOptNeeded(
+ classPathElement, "*", instructionSet, false /* defer */);
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ installer.dexopt(classPathElement, Process.SYSTEM_UID, false,
+ instructionSet, dexoptNeeded);
}
}
} catch (IOException ioe) {
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index be9945d..2219ad1 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -24,7 +24,9 @@
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
+import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
+import android.util.Size;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -32,19 +34,28 @@
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.Transformation;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
+import android.widget.ListView;
import android.widget.PopupWindow;
-
-import com.android.internal.R;
-import com.android.internal.util.Preconditions;
+import android.widget.TextView;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
+import com.android.internal.R;
+import com.android.internal.util.Preconditions;
+
/**
* A floating toolbar for showing contextual menu items.
* This view shows as many menu item buttons as can fit in the horizontal toolbar and the
@@ -53,6 +64,9 @@
*/
public final class FloatingToolbar {
+ // This class is responsible for the public API of the floating toolbar.
+ // It delegates rendering operations to the FloatingToolbarPopup.
+
private static final MenuItem.OnMenuItemClickListener NO_OP_MENUITEM_CLICK_LISTENER =
new MenuItem.OnMenuItemClickListener() {
@Override
@@ -63,17 +77,6 @@
private final Context mContext;
private final FloatingToolbarPopup mPopup;
- private final ViewGroup mMenuItemButtonsContainer;
- private final View.OnClickListener mMenuItemButtonOnClickListener =
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (v.getTag() instanceof MenuItem) {
- mMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag());
- mPopup.dismiss();
- }
- }
- };
private final Rect mContentRect = new Rect();
private final Point mCoordinates = new Point();
@@ -81,17 +84,17 @@
private Menu mMenu;
private List<CharSequence> mShowingTitles = new ArrayList<CharSequence>();
private MenuItem.OnMenuItemClickListener mMenuItemClickListener = NO_OP_MENUITEM_CLICK_LISTENER;
- private View mOpenOverflowButton;
private int mSuggestedWidth;
+ private boolean mWidthChanged = true;
+ private int mOverflowDirection;
/**
* Initializes a floating toolbar.
*/
public FloatingToolbar(Context context, Window window) {
mContext = Preconditions.checkNotNull(context);
- mPopup = new FloatingToolbarPopup(Preconditions.checkNotNull(window.getDecorView()));
- mMenuItemButtonsContainer = createMenuButtonsContainer(context);
+ mPopup = new FloatingToolbarPopup(window.getDecorView());
}
/**
@@ -137,6 +140,10 @@
* toolbar.
*/
public FloatingToolbar setSuggestedWidth(int suggestedWidth) {
+ // Check if there's been a substantial width spec change.
+ int difference = Math.abs(suggestedWidth - mSuggestedWidth);
+ mWidthChanged = difference > (mSuggestedWidth * 0.2);
+
mSuggestedWidth = suggestedWidth;
return this;
}
@@ -146,16 +153,18 @@
*/
public FloatingToolbar show() {
List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
- if (hasContentChanged(menuItems) || hasWidthChanged()) {
+ if (!isCurrentlyShowing(menuItems) || mWidthChanged) {
mPopup.dismiss();
- layoutMenuItemButtons(menuItems);
+ mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth);
mShowingTitles = getMenuItemTitles(menuItems);
}
refreshCoordinates();
+ mPopup.setOverflowDirection(mOverflowDirection);
mPopup.updateCoordinates(mCoordinates.x, mCoordinates.y);
if (!mPopup.isShowing()) {
mPopup.show(mCoordinates.x, mCoordinates.y);
}
+ mWidthChanged = false;
return this;
}
@@ -189,45 +198,26 @@
* Refreshes {@link #mCoordinates} with values based on {@link #mContentRect}.
*/
private void refreshCoordinates() {
- int popupWidth = mPopup.getWidth();
- int popupHeight = mPopup.getHeight();
- if (!mPopup.isShowing()) {
- // Popup isn't yet shown, get estimated size from the menu item buttons container.
- mMenuItemButtonsContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- popupWidth = mMenuItemButtonsContainer.getMeasuredWidth();
- popupHeight = mMenuItemButtonsContainer.getMeasuredHeight();
- }
- int x = mContentRect.centerX() - popupWidth / 2;
+ int x = mContentRect.centerX() - mPopup.getWidth() / 2;
int y;
- if (shouldDisplayAtTopOfContent()) {
- y = mContentRect.top - popupHeight;
+ if (mContentRect.top > mPopup.getHeight()) {
+ y = mContentRect.top - mPopup.getHeight();
+ mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_UP;
+ } else if (mContentRect.top > getEstimatedToolbarHeight(mContext)) {
+ y = mContentRect.top - getEstimatedToolbarHeight(mContext);
+ mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN;
} else {
y = mContentRect.bottom;
+ mOverflowDirection = FloatingToolbarPopup.OVERFLOW_DIRECTION_DOWN;
}
mCoordinates.set(x, y);
}
/**
- * Returns true if this floating toolbar's menu items have been reordered or changed.
+ * Returns true if this floating toolbar is currently showing the specified menu items.
*/
- private boolean hasContentChanged(List<MenuItem> menuItems) {
- return !mShowingTitles.equals(getMenuItemTitles(menuItems));
- }
-
- /**
- * Returns true if there is a significant change in width of the toolbar.
- */
- private boolean hasWidthChanged() {
- int actualWidth = mPopup.getWidth();
- int difference = Math.abs(actualWidth - mSuggestedWidth);
- return difference > (actualWidth * 0.2);
- }
-
- /**
- * Returns true if the preferred positioning of the toolbar is above the content rect.
- */
- private boolean shouldDisplayAtTopOfContent() {
- return mContentRect.top - getMinimumOverflowHeight(mContext) > 0;
+ private boolean isCurrentlyShowing(List<MenuItem> menuItems) {
+ return mShowingTitles.equals(getMenuItemTitles(menuItems));
}
/**
@@ -258,83 +248,767 @@
return titles;
}
- private void layoutMenuItemButtons(List<MenuItem> menuItems) {
- final int toolbarWidth = getAdjustedToolbarWidth(mContext, mSuggestedWidth)
- // Reserve space for the "open overflow" button.
- - getEstimatedOpenOverflowButtonWidth(mContext);
- int availableWidth = toolbarWidth;
- LinkedList<MenuItem> remainingMenuItems = new LinkedList<MenuItem>(menuItems);
+ /**
+ * A popup window used by the floating toolbar.
+ *
+ * This class is responsible for the rendering/animation of the floating toolbar.
+ * It can hold one of 2 panels (i.e. main panel and overflow panel) at a time.
+ * It delegates specific panel functionality to the appropriate panel.
+ */
+ private static final class FloatingToolbarPopup {
- mMenuItemButtonsContainer.removeAllViews();
+ public static final int OVERFLOW_DIRECTION_UP = 0;
+ public static final int OVERFLOW_DIRECTION_DOWN = 1;
- boolean isFirstItem = true;
- while (!remainingMenuItems.isEmpty()) {
- final MenuItem menuItem = remainingMenuItems.peek();
- Button menuItemButton = createMenuItemButton(mContext, menuItem);
+ private final View mParent;
+ private final PopupWindow mPopupWindow;
+ private final ViewGroup mContentContainer;
+ private final int mPadding;
- // Adding additional left padding for the first button to even out button spacing.
- if (isFirstItem) {
- menuItemButton.setPadding(
- 2 * menuItemButton.getPaddingLeft(),
- menuItemButton.getPaddingTop(),
- menuItemButton.getPaddingRight(),
- menuItemButton.getPaddingBottom());
- isFirstItem = false;
+ private final Animation.AnimationListener mOnOverflowOpened =
+ new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {}
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // This animation should never be run if the overflow panel has not been
+ // initialized.
+ Preconditions.checkNotNull(mOverflowPanel);
+ mContentContainer.removeAllViews();
+ mContentContainer.addView(mOverflowPanel.getView());
+ mOverflowPanel.fadeIn(true);
+ setContentAreaAsTouchableSurface();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {}
+ };
+ private final Animation.AnimationListener mOnOverflowClosed =
+ new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {}
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // This animation should never be run if the main panel has not been
+ // initialized.
+ Preconditions.checkNotNull(mMainPanel);
+ mContentContainer.removeAllViews();
+ mContentContainer.addView(mMainPanel.getView());
+ mMainPanel.fadeIn(true);
+ setContentAreaAsTouchableSurface();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ };
+ private final AnimatorSet mGrowFadeInFromBottomAnimation;
+ private final AnimatorSet mShrinkFadeOutFromBottomAnimation;
+
+ private final Runnable mOpenOverflow = new Runnable() {
+ @Override
+ public void run() {
+ openOverflow();
+ }
+ };
+ private final Runnable mCloseOverflow = new Runnable() {
+ @Override
+ public void run() {
+ closeOverflow();
+ }
+ };
+
+ private final Region mTouchableRegion = new Region();
+
+ private boolean mDismissAnimating;
+
+ private FloatingToolbarOverflowPanel mOverflowPanel;
+ private FloatingToolbarMainPanel mMainPanel;
+ private int mOverflowDirection;
+
+ /**
+ * Initializes a new floating toolbar popup.
+ *
+ * @param parent A parent view to get the {@link android.view.View#getWindowToken()} token
+ * from.
+ */
+ public FloatingToolbarPopup(View parent) {
+ mParent = Preconditions.checkNotNull(parent);
+ mContentContainer = createContentContainer(parent.getContext());
+ mPopupWindow = createPopupWindow(mContentContainer);
+ mGrowFadeInFromBottomAnimation = createGrowFadeInFromBottom(mContentContainer);
+ mShrinkFadeOutFromBottomAnimation = createShrinkFadeOutFromBottomAnimation(
+ mContentContainer,
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPopupWindow.dismiss();
+ mDismissAnimating = false;
+ setMainPanelAsContent();
+ }
+ });
+ // Make the touchable area of this popup be the area specified by mTouchableRegion.
+ mPopupWindow.getContentView()
+ .getRootView()
+ .getViewTreeObserver()
+ .addOnComputeInternalInsetsListener(
+ new ViewTreeObserver.OnComputeInternalInsetsListener() {
+ public void onComputeInternalInsets(
+ ViewTreeObserver.InternalInsetsInfo info) {
+ info.contentInsets.setEmpty();
+ info.visibleInsets.setEmpty();
+ info.touchableRegion.set(mTouchableRegion);
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo
+ .TOUCHABLE_INSETS_REGION);
+ }
+ });
+ mPadding = parent.getResources().getDimensionPixelSize(R.dimen.floating_toolbar_margin);
+ }
+
+ /**
+ * Lays out buttons for the specified menu items.
+ */
+ public void layoutMenuItems(List<MenuItem> menuItems,
+ MenuItem.OnMenuItemClickListener menuItemClickListener, int suggestedWidth) {
+ mContentContainer.removeAllViews();
+ if (mMainPanel == null) {
+ mMainPanel = new FloatingToolbarMainPanel(mParent.getContext(), mOpenOverflow);
+ }
+ List<MenuItem> overflowMenuItems =
+ mMainPanel.layoutMenuItems(menuItems, suggestedWidth);
+ mMainPanel.setOnMenuItemClickListener(menuItemClickListener);
+ if (!overflowMenuItems.isEmpty()) {
+ if (mOverflowPanel == null) {
+ mOverflowPanel =
+ new FloatingToolbarOverflowPanel(mParent.getContext(), mCloseOverflow);
+ }
+ mOverflowPanel.setMenuItems(overflowMenuItems);
+ mOverflowPanel.setOnMenuItemClickListener(menuItemClickListener);
+ }
+ updatePopupSize();
+ }
+
+ /**
+ * Shows this popup at the specified coordinates.
+ * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
+ */
+ public void show(int x, int y) {
+ if (isShowing()) {
+ return;
}
- // Adding additional right padding for the last button to even out button spacing.
- if (remainingMenuItems.size() == 1) {
- menuItemButton.setPadding(
- menuItemButton.getPaddingLeft(),
- menuItemButton.getPaddingTop(),
- 2 * menuItemButton.getPaddingRight(),
- menuItemButton.getPaddingBottom());
+ stopDismissAnimation();
+ preparePopupContent();
+ mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, x, y);
+ growFadeInFromBottom();
+ }
+
+ /**
+ * Gets rid of this popup. If the popup isn't currently showing, this will be a no-op.
+ */
+ public void dismiss() {
+ if (!isShowing()) {
+ return;
}
- menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
- if (menuItemButtonWidth <= availableWidth) {
- menuItemButton.setTag(menuItem);
- menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
- mMenuItemButtonsContainer.addView(menuItemButton);
- menuItemButton.getLayoutParams().width = menuItemButtonWidth;
- availableWidth -= menuItemButtonWidth;
- remainingMenuItems.pop();
- } else {
- // The "open overflow" button launches the vertical overflow from the
- // floating toolbar.
- createOpenOverflowButtonIfNotExists();
- mMenuItemButtonsContainer.addView(mOpenOverflowButton);
- break;
+ mDismissAnimating = true;
+ shrinkFadeOutFromBottom();
+ setZeroTouchableSurface();
+ }
+
+ /**
+ * Returns {@code true} if this popup is currently showing. {@code false} otherwise.
+ */
+ public boolean isShowing() {
+ return mPopupWindow.isShowing() && !mDismissAnimating;
+ }
+
+ /**
+ * Updates the coordinates of this popup.
+ * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
+ */
+ public void updateCoordinates(int x, int y) {
+ if (mDismissAnimating) {
+ // Already being dismissed. Ignore.
+ return;
+ }
+
+ preparePopupContent();
+ mPopupWindow.update(x, y, getWidth(), getHeight());
+ }
+
+ /**
+ * Sets the direction in which the overflow will open. i.e. up or down.
+ *
+ * @param overflowDirection Either {@link #OVERFLOW_DIRECTION_UP}
+ * or {@link #OVERFLOW_DIRECTION_DOWN}.
+ */
+ public void setOverflowDirection(int overflowDirection) {
+ mOverflowDirection = overflowDirection;
+ if (mOverflowPanel != null) {
+ mOverflowPanel.setOverflowDirection(mOverflowDirection);
}
}
- mPopup.setContentView(mMenuItemButtonsContainer);
+
+ /**
+ * Returns the width of this popup.
+ */
+ public int getWidth() {
+ return mPopupWindow.getWidth();
+ }
+
+ /**
+ * Returns the height of this popup.
+ */
+ public int getHeight() {
+ return mPopupWindow.getHeight();
+ }
+
+ /**
+ * Returns the context this popup is running in.
+ */
+ public Context getContext() {
+ return mContentContainer.getContext();
+ }
+
+ /**
+ * Performs the "grow and fade in from the bottom" animation on the floating popup.
+ */
+ private void growFadeInFromBottom() {
+ mGrowFadeInFromBottomAnimation.start();
+ }
+
+ /**
+ * Performs the "shrink and fade out from bottom" animation on the floating popup.
+ */
+ private void shrinkFadeOutFromBottom() {
+ mShrinkFadeOutFromBottomAnimation.start();
+ }
+
+ private void stopDismissAnimation() {
+ mDismissAnimating = false;
+ mShrinkFadeOutFromBottomAnimation.cancel();
+ }
+
+ /**
+ * Opens the floating toolbar overflow.
+ * This method should not be called if menu items have not been laid out with
+ * {@link #layoutMenuItems(List, MenuItem.OnMenuItemClickListener, int)}.
+ *
+ * @throws IllegalStateException if called when menu items have not been laid out.
+ */
+ private void openOverflow() {
+ Preconditions.checkNotNull(mMainPanel);
+ Preconditions.checkNotNull(mOverflowPanel);
+
+ mMainPanel.fadeOut(true);
+ Size overflowPanelSize = mOverflowPanel.measure();
+ final int targetWidth = getOverflowWidth(mParent.getContext());
+ final int targetHeight = overflowPanelSize.getHeight();
+ final boolean morphUpwards = (mOverflowDirection == OVERFLOW_DIRECTION_UP);
+ final int startWidth = mContentContainer.getWidth();
+ final int startHeight = mContentContainer.getHeight();
+ final float startY = mContentContainer.getY();
+ final float right = mContentContainer.getX() + mContentContainer.getWidth();
+ Animation widthAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
+ int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
+ params.width = startWidth + deltaWidth;
+ mContentContainer.setLayoutParams(params);
+ mContentContainer.setX(right - mContentContainer.getWidth());
+ }
+ };
+ Animation heightAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
+ int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
+ params.height = startHeight + deltaHeight;
+ mContentContainer.setLayoutParams(params);
+ if (morphUpwards) {
+ float y = startY - (mContentContainer.getHeight() - startHeight);
+ mContentContainer.setY(y);
+ }
+ }
+ };
+ widthAnimation.setDuration(240);
+ heightAnimation.setDuration(180);
+ heightAnimation.setStartOffset(60);
+ AnimationSet animation = new AnimationSet(true);
+ animation.setAnimationListener(mOnOverflowOpened);
+ animation.addAnimation(widthAnimation);
+ animation.addAnimation(heightAnimation);
+ mContentContainer.startAnimation(animation);
+ }
+
+ /**
+ * Opens the floating toolbar overflow.
+ * This method should not be called if menu items have not been laid out with
+ * {@link #layoutMenuItems(java.util.List, MenuItem.OnMenuItemClickListener, int)}.
+ *
+ * @throws IllegalStateException
+ */
+ private void closeOverflow() {
+ Preconditions.checkNotNull(mMainPanel);
+ Preconditions.checkNotNull(mOverflowPanel);
+
+ mOverflowPanel.fadeOut(true);
+ Size mainPanelSize = mMainPanel.measure();
+ final int targetWidth = mainPanelSize.getWidth();
+ final int targetHeight = mainPanelSize.getHeight();
+ final int startWidth = mContentContainer.getWidth();
+ final int startHeight = mContentContainer.getHeight();
+ final float right = mContentContainer.getX() + mContentContainer.getWidth();
+ final float bottom = mContentContainer.getY() + mContentContainer.getHeight();
+ final boolean morphedUpwards = (mOverflowDirection == OVERFLOW_DIRECTION_UP);
+ Animation widthAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
+ int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
+ params.width = startWidth + deltaWidth;
+ mContentContainer.setLayoutParams(params);
+ mContentContainer.setX(right - mContentContainer.getWidth());
+ }
+ };
+ Animation heightAnimation = new Animation() {
+ @Override
+ protected void applyTransformation(float interpolatedTime, Transformation t) {
+ ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
+ int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
+ params.height = startHeight + deltaHeight;
+ mContentContainer.setLayoutParams(params);
+ if (morphedUpwards) {
+ mContentContainer.setY(bottom - mContentContainer.getHeight());
+ }
+ }
+ };
+ widthAnimation.setDuration(150);
+ widthAnimation.setStartOffset(150);
+ heightAnimation.setDuration(210);
+ AnimationSet animation = new AnimationSet(true);
+ animation.setAnimationListener(mOnOverflowClosed);
+ animation.addAnimation(widthAnimation);
+ animation.addAnimation(heightAnimation);
+ mContentContainer.startAnimation(animation);
+ }
+
+ /**
+ * Prepares the content container for show and update calls.
+ */
+ private void preparePopupContent() {
+ // Do not call this method if main view panel has not been initialized.
+ Preconditions.checkNotNull(mMainPanel);
+
+ // If we're yet to show the popup, set the container visibility to zero.
+ // The "show" animation will make this visible.
+ if (!mPopupWindow.isShowing()) {
+ mContentContainer.setAlpha(0);
+ }
+
+ // Make sure panels are visible.
+ mMainPanel.fadeIn(false);
+ if (mOverflowPanel != null) {
+ mOverflowPanel.fadeIn(false);
+ }
+
+ // Make sure a panel is set as the content.
+ if (mContentContainer.getChildCount() == 0) {
+ mContentContainer.addView(mMainPanel.getView());
+ }
+
+ // Make sure the main panel is at the correct position.
+ if (mContentContainer.getChildAt(0) == mMainPanel.getView()) {
+ mContentContainer.setX(mPadding);
+ float y = mPadding;
+ if (mOverflowDirection == OVERFLOW_DIRECTION_UP) {
+ y = getHeight() - getEstimatedToolbarHeight(mParent.getContext()) - mPadding;
+ }
+ mContentContainer.setY(y);
+ }
+
+ setContentAreaAsTouchableSurface();
+ }
+
+ /**
+ * Sets the current content to be the main view panel.
+ */
+ private void setMainPanelAsContent() {
+ mContentContainer.removeAllViews();
+ Size mainPanelSize = mMainPanel.measure();
+ ViewGroup.LayoutParams params = mContentContainer.getLayoutParams();
+ params.width = mainPanelSize.getWidth();
+ params.height = mainPanelSize.getHeight();
+ mContentContainer.setLayoutParams(params);
+ mContentContainer.addView(mMainPanel.getView());
+ }
+
+ private void updatePopupSize() {
+ int width = 0;
+ int height = 0;
+ if (mMainPanel != null) {
+ Size mainPanelSize = mMainPanel.measure();
+ width = mainPanelSize.getWidth();
+ height = mainPanelSize.getHeight();
+ }
+ if (mOverflowPanel != null) {
+ Size overflowPanelSize = mOverflowPanel.measure();
+ width = Math.max(width, overflowPanelSize.getWidth());
+ height = Math.max(height, overflowPanelSize.getHeight());
+ }
+ mPopupWindow.setWidth(width + mPadding * 2);
+ mPopupWindow.setHeight(height + mPadding * 2);
+ }
+
+ /**
+ * Sets the touchable region of this popup to be zero. This means that all touch events on
+ * this popup will go through to the surface behind it.
+ */
+ private void setZeroTouchableSurface() {
+ mTouchableRegion.setEmpty();
+ }
+
+ /**
+ * Sets the touchable region of this popup to be the area occupied by its content.
+ */
+ private void setContentAreaAsTouchableSurface() {
+ if (!mPopupWindow.isShowing()) {
+ mContentContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ }
+ int width = mContentContainer.getMeasuredWidth();
+ int height = mContentContainer.getMeasuredHeight();
+ mTouchableRegion.set(
+ (int) mContentContainer.getX(),
+ (int) mContentContainer.getY(),
+ (int) mContentContainer.getX() + width,
+ (int) mContentContainer.getY() + height);
+ }
}
/**
- * Creates and returns the button that opens the vertical overflow.
+ * A widget that holds the primary menu items in the floating toolbar.
*/
- private void createOpenOverflowButtonIfNotExists() {
- mOpenOverflowButton = (ImageButton) LayoutInflater.from(mContext)
- .inflate(R.layout.floating_popup_open_overflow_button, null);
- mOpenOverflowButton.setOnClickListener(
+ private static final class FloatingToolbarMainPanel {
+
+ private final Context mContext;
+ private final ViewGroup mContentView;
+ private final View.OnClickListener mMenuItemButtonOnClickListener =
new View.OnClickListener() {
@Override
public void onClick(View v) {
- // Open the overflow.
+ if (v.getTag() instanceof MenuItem) {
+ if (mOnMenuItemClickListener != null) {
+ mOnMenuItemClickListener.onMenuItemClick((MenuItem) v.getTag());
+ }
+ }
}
- });
+ };
+ private final ViewFader viewFader;
+ private final Runnable mOpenOverflow;
+
+ private View mOpenOverflowButton;
+ private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
+
+ /**
+ * Initializes a floating toolbar popup main view panel.
+ *
+ * @param context
+ * @param openOverflow The code that opens the toolbar popup overflow.
+ */
+ public FloatingToolbarMainPanel(Context context, Runnable openOverflow) {
+ mContext = Preconditions.checkNotNull(context);
+ mContentView = new LinearLayout(context);
+ viewFader = new ViewFader(mContentView);
+ mOpenOverflow = Preconditions.checkNotNull(openOverflow);
+ }
+
+ /**
+ * Fits as many menu items in the main panel and returns a list of the menu items that
+ * were not fit in.
+ *
+ * @return The menu items that are not included in this main panel.
+ */
+ public List<MenuItem> layoutMenuItems(List<MenuItem> menuItems, int suggestedWidth) {
+ final int toolbarWidth = getAdjustedToolbarWidth(mContext, suggestedWidth)
+ // Reserve space for the "open overflow" button.
+ - getEstimatedOpenOverflowButtonWidth(mContext);
+
+ int availableWidth = toolbarWidth;
+ final LinkedList<MenuItem> remainingMenuItems = new LinkedList<MenuItem>(menuItems);
+
+ mContentView.removeAllViews();
+
+ boolean isFirstItem = true;
+ while (!remainingMenuItems.isEmpty()) {
+ final MenuItem menuItem = remainingMenuItems.peek();
+ Button menuItemButton = createMenuItemButton(mContext, menuItem);
+
+ // Adding additional left padding for the first button to even out button spacing.
+ if (isFirstItem) {
+ menuItemButton.setPadding(
+ 2 * menuItemButton.getPaddingLeft(),
+ menuItemButton.getPaddingTop(),
+ menuItemButton.getPaddingRight(),
+ menuItemButton.getPaddingBottom());
+ isFirstItem = false;
+ }
+
+ // Adding additional right padding for the last button to even out button spacing.
+ if (remainingMenuItems.size() == 1) {
+ menuItemButton.setPadding(
+ menuItemButton.getPaddingLeft(),
+ menuItemButton.getPaddingTop(),
+ 2 * menuItemButton.getPaddingRight(),
+ menuItemButton.getPaddingBottom());
+ }
+
+ menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
+ if (menuItemButtonWidth <= availableWidth) {
+ menuItemButton.setTag(menuItem);
+ menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
+ mContentView.addView(menuItemButton);
+ ViewGroup.LayoutParams params = menuItemButton.getLayoutParams();
+ params.width = menuItemButtonWidth;
+ menuItemButton.setLayoutParams(params);
+ availableWidth -= menuItemButtonWidth;
+ remainingMenuItems.pop();
+ } else {
+ if (mOpenOverflowButton == null) {
+ mOpenOverflowButton = (ImageButton) LayoutInflater.from(mContext)
+ .inflate(R.layout.floating_popup_open_overflow_button, null);
+ mOpenOverflowButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mOpenOverflowButton != null) {
+ mOpenOverflow.run();
+ }
+ }
+ });
+ }
+ mContentView.addView(mOpenOverflowButton);
+ break;
+ }
+ }
+ return remainingMenuItems;
+ }
+
+ public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
+ mOnMenuItemClickListener = listener;
+ }
+
+ public View getView() {
+ return mContentView;
+ }
+
+ public void fadeIn(boolean animate) {
+ viewFader.fadeIn(animate);
+ }
+
+ public void fadeOut(boolean animate) {
+ viewFader.fadeOut(animate);
+ }
+
+ /**
+ * Returns how big this panel's view should be.
+ * This method should only be called when the view has not been attached to a parent
+ * otherwise it will throw an illegal state.
+ */
+ public Size measure() throws IllegalStateException {
+ Preconditions.checkState(mContentView.getParent() == null);
+ mContentView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ return new Size(mContentView.getMeasuredWidth(), mContentView.getMeasuredHeight());
+ }
}
+
/**
- * Creates and returns a floating toolbar menu buttons container.
+ * A widget that holds the overflow items in the floating toolbar.
*/
- private static ViewGroup createMenuButtonsContainer(Context context) {
- return (ViewGroup) LayoutInflater.from(context)
- .inflate(R.layout.floating_popup_container, null);
+ private static final class FloatingToolbarOverflowPanel {
+
+ private final LinearLayout mContentView;
+ private final ViewGroup mBackButtonContainer;
+ private final View mBackButton;
+ private final ListView mListView;
+ private final ViewFader mViewFader;
+ private final Runnable mCloseOverflow;
+
+ private MenuItem.OnMenuItemClickListener mOnMenuItemClickListener;
+
+ /**
+ * Initializes a floating toolbar popup overflow view panel.
+ *
+ * @param context
+ * @param closeOverflow The code that closes the toolbar popup's overflow.
+ */
+ public FloatingToolbarOverflowPanel(Context context, Runnable closeOverflow) {
+ mCloseOverflow = Preconditions.checkNotNull(closeOverflow);
+
+ mContentView = new LinearLayout(context);
+ mContentView.setOrientation(LinearLayout.VERTICAL);
+ mViewFader = new ViewFader(mContentView);
+
+ mBackButton = LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_close_overflow_button, null);
+ mBackButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mCloseOverflow.run();
+ }
+ });
+ mBackButtonContainer = new LinearLayout(context);
+ mBackButtonContainer.addView(mBackButton);
+
+ mListView = createOverflowListView(context);
+ mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ MenuItem menuItem = (MenuItem) mListView.getAdapter().getItem(position);
+ if (mOnMenuItemClickListener != null) {
+ mOnMenuItemClickListener.onMenuItemClick(menuItem);
+ }
+ }
+ });
+
+ mContentView.addView(mListView);
+ mContentView.addView(mBackButtonContainer);
+ }
+
+ /**
+ * Sets the menu items to be displayed in the overflow.
+ */
+ public void setMenuItems(List<MenuItem> menuItems) {
+ ArrayAdapter overflowListViewAdapter = (ArrayAdapter) mListView.getAdapter();
+ overflowListViewAdapter.clear();
+ overflowListViewAdapter.addAll(menuItems);
+ setListViewHeight();
+ }
+
+ public void setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener listener) {
+ mOnMenuItemClickListener = listener;
+ }
+
+ /**
+ * Notifies the overflow of the current direction in which the overflow will be opened.
+ *
+ * @param overflowDirection {@link FloatingToolbarPopup#OVERFLOW_DIRECTION_UP}
+ * or {@link FloatingToolbarPopup#OVERFLOW_DIRECTION_DOWN}.
+ */
+ public void setOverflowDirection(int overflowDirection) {
+ mContentView.removeView(mBackButtonContainer);
+ int index = (overflowDirection == FloatingToolbarPopup.OVERFLOW_DIRECTION_UP)? 1 : 0;
+ mContentView.addView(mBackButtonContainer, index);
+ }
+
+ /**
+ * Returns the content view of the overflow.
+ */
+ public View getView() {
+ return mContentView;
+ }
+
+ public void fadeIn(boolean animate) {
+ mViewFader.fadeIn(animate);
+ }
+
+ public void fadeOut(boolean animate) {
+ mViewFader.fadeOut(animate);
+ }
+
+ /**
+ * Returns how big this panel's view should be.
+ * This method should only be called when the view has not been attached to a parent.
+ *
+ * @throws IllegalStateException
+ */
+ public Size measure() {
+ Preconditions.checkState(mContentView.getParent() == null);
+ mContentView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ return new Size(mContentView.getMeasuredWidth(), mContentView.getMeasuredHeight());
+ }
+
+ private void setListViewHeight() {
+ int itemHeight = getEstimatedToolbarHeight(mContentView.getContext());
+ int height = mListView.getAdapter().getCount() * itemHeight;
+ int maxHeight = mContentView.getContext().getResources().
+ getDimensionPixelSize(R.dimen.floating_toolbar_minimum_overflow_height);
+ ViewGroup.LayoutParams params = mListView.getLayoutParams();
+ params.height = Math.min(height, maxHeight);
+ mListView.setLayoutParams(params);
+ }
+
+ private static ListView createOverflowListView(final Context context) {
+ final ListView overflowListView = new ListView(context);
+ overflowListView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ overflowListView.setDivider(null);
+ overflowListView.setDividerHeight(0);
+ final ArrayAdapter overflowListViewAdapter =
+ new ArrayAdapter<MenuItem>(context, 0) {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView menuButton;
+ if (convertView != null) {
+ menuButton = (TextView) convertView;
+ } else {
+ menuButton = createOverflowMenuItemButton(context);
+ }
+ MenuItem menuItem = getItem(position);
+ menuButton.setText(menuItem.getTitle());
+ menuButton.setContentDescription(menuItem.getTitle());
+ return menuButton;
+ }
+ };
+ overflowListView.setAdapter(overflowListViewAdapter);
+ return overflowListView;
+ }
}
+
+ /**
+ * A helper for fading in or out a view.
+ */
+ private static final class ViewFader {
+
+ private static final int FADE_OUT_DURATION = 250;
+ private static final int FADE_IN_DURATION = 150;
+
+ private final View mView;
+ private final ObjectAnimator mFadeOutAnimation;
+ private final ObjectAnimator mFadeInAnimation;
+
+ private ViewFader(View view) {
+ mView = Preconditions.checkNotNull(view);
+ mFadeOutAnimation = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0)
+ .setDuration(FADE_OUT_DURATION);
+ mFadeInAnimation = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1)
+ .setDuration(FADE_IN_DURATION);
+ }
+
+ public void fadeIn(boolean animate) {
+ if (animate) {
+ mFadeInAnimation.start();
+ } else {
+ mView.setAlpha(1);
+ }
+ }
+
+ public void fadeOut(boolean animate) {
+ if (animate) {
+ mFadeOutAnimation.start();
+ } else {
+ mView.setAlpha(0);
+ }
+ }
+ }
+
+
/**
* Creates and returns a menu button for the specified menu item.
*/
@@ -346,9 +1020,70 @@
return menuItemButton;
}
- private static int getMinimumOverflowHeight(Context context) {
- return context.getResources().
- getDimensionPixelSize(R.dimen.floating_toolbar_minimum_overflow_height);
+ /**
+ * Creates and returns a styled floating toolbar overflow list view item.
+ */
+ private static TextView createOverflowMenuItemButton(Context context) {
+ return (TextView) LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_overflow_list_item, null);
+ }
+
+ private static ViewGroup createContentContainer(Context context) {
+ return (ViewGroup) LayoutInflater.from(context)
+ .inflate(R.layout.floating_popup_container, null);
+ }
+
+ private static PopupWindow createPopupWindow(View content) {
+ ViewGroup popupContentHolder = new LinearLayout(content.getContext());
+ PopupWindow popupWindow = new PopupWindow(popupContentHolder);
+ popupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
+ popupWindow.setAnimationStyle(0);
+ popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ content.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+ popupContentHolder.addView(content);
+ return popupWindow;
+ }
+
+ /**
+ * Creates a "grow and fade in from the bottom" animation for the specified view.
+ *
+ * @param view The view to animate
+ */
+ private static AnimatorSet createGrowFadeInFromBottom(View view) {
+ AnimatorSet growFadeInFromBottomAnimation = new AnimatorSet();
+ growFadeInFromBottomAnimation.playTogether(
+ ObjectAnimator.ofFloat(view, View.SCALE_X, 0.5f, 1).setDuration(125),
+ ObjectAnimator.ofFloat(view, View.SCALE_Y, 0.5f, 1).setDuration(125),
+ ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(75));
+ growFadeInFromBottomAnimation.setStartDelay(50);
+ return growFadeInFromBottomAnimation;
+ }
+
+ /**
+ * Creates a "shrink and fade out from bottom" animation for the specified view.
+ *
+ * @param view The view to animate
+ * @param listener The animation listener
+ */
+ private static AnimatorSet createShrinkFadeOutFromBottomAnimation(
+ View view, Animator.AnimatorListener listener) {
+ AnimatorSet shrinkFadeOutFromBottomAnimation = new AnimatorSet();
+ shrinkFadeOutFromBottomAnimation.playTogether(
+ ObjectAnimator.ofFloat(view, View.SCALE_Y, 1, 0.5f).setDuration(125),
+ ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(75));
+ shrinkFadeOutFromBottomAnimation.setStartDelay(150);
+ shrinkFadeOutFromBottomAnimation.addListener(listener);
+ return shrinkFadeOutFromBottomAnimation;
+ }
+
+ private static int getOverflowWidth(Context context) {
+ return context.getResources()
+ .getDimensionPixelSize(R.dimen.floating_toolbar_overflow_width);
+ }
+
+ private static int getEstimatedToolbarHeight(Context context) {
+ return context.getResources().getDimensionPixelSize(R.dimen.floating_toolbar_height);
}
private static int getEstimatedOpenOverflowButtonWidth(Context context) {
@@ -367,230 +1102,26 @@
/**
* Returns the device's screen width.
*/
- public static int getScreenWidth(Context context) {
+ private static int getScreenWidth(Context context) {
return context.getResources().getDisplayMetrics().widthPixels;
}
/**
* Returns the device's screen height.
*/
- public static int getScreenHeight(Context context) {
+ private static int getScreenHeight(Context context) {
return context.getResources().getDisplayMetrics().heightPixels;
}
-
/**
- * A popup window used by the floating toolbar.
+ * Returns value, restricted to the range min->max (inclusive).
+ * If maximum is less than minimum, the result is undefined.
+ *
+ * @param value The value to clamp.
+ * @param minimum The minimum value in the range.
+ * @param maximum The maximum value in the range. Must not be less than minimum.
*/
- private static final class FloatingToolbarPopup {
-
- private final View mParent;
- private final PopupWindow mPopupWindow;
- private final ViewGroup mContentContainer;
- private final Animator.AnimatorListener mOnDismissEnd =
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mPopupWindow.dismiss();
- mDismissAnimating = false;
- }
- };
- private final AnimatorSet mGrowFadeInFromBottomAnimation;
- private final AnimatorSet mShrinkFadeOutFromBottomAnimation;
-
- private boolean mDismissAnimating;
-
- /**
- * Initializes a new floating bar popup.
- *
- * @param parent A parent view to get the {@link View#getWindowToken()} token from.
- */
- public FloatingToolbarPopup(View parent) {
- mParent = Preconditions.checkNotNull(parent);
- mContentContainer = createContentContainer(parent.getContext());
- mPopupWindow = createPopupWindow(mContentContainer);
- mGrowFadeInFromBottomAnimation = createGrowFadeInFromBottom(mContentContainer);
- mShrinkFadeOutFromBottomAnimation =
- createShrinkFadeOutFromBottomAnimation(mContentContainer, mOnDismissEnd);
- }
-
- /**
- * Shows this popup at the specified coordinates.
- * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
- * If this popup is already showing, this will be a no-op.
- */
- public void show(int x, int y) {
- if (isShowing()) {
- updateCoordinates(x, y);
- return;
- }
-
- mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, 0, 0);
- positionOnScreen(x, y);
- growFadeInFromBottom();
-
- mDismissAnimating = false;
- }
-
- /**
- * Gets rid of this popup. If the popup isn't currently showing, this will be a no-op.
- */
- public void dismiss() {
- if (!isShowing()) {
- return;
- }
-
- if (mDismissAnimating) {
- // This window is already dismissing. Don't restart the animation.
- return;
- }
- mDismissAnimating = true;
- shrinkFadeOutFromBottom();
- }
-
- /**
- * Returns {@code true} if this popup is currently showing. {@code false} otherwise.
- */
- public boolean isShowing() {
- return mPopupWindow.isShowing() && !mDismissAnimating;
- }
-
- /**
- * Updates the coordinates of this popup.
- * The specified coordinates may be adjusted to make sure the popup is entirely on-screen.
- */
- public void updateCoordinates(int x, int y) {
- if (isShowing()) {
- positionOnScreen(x, y);
- }
- }
-
- /**
- * Sets the content of this popup.
- */
- public void setContentView(View view) {
- Preconditions.checkNotNull(view);
- mContentContainer.removeAllViews();
- mContentContainer.addView(view);
- }
-
- /**
- * Returns the width of this popup.
- */
- public int getWidth() {
- return mContentContainer.getWidth();
- }
-
- /**
- * Returns the height of this popup.
- */
- public int getHeight() {
- return mContentContainer.getHeight();
- }
-
- /**
- * Returns the context this popup is running in.
- */
- public Context getContext() {
- return mContentContainer.getContext();
- }
-
- private void positionOnScreen(int x, int y) {
- if (getWidth() == 0) {
- // content size is yet to be measured.
- mContentContainer.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- }
- x = clamp(x, 0, getScreenWidth(getContext()) - getWidth());
- y = clamp(y, 0, getScreenHeight(getContext()) - getHeight());
-
- // Position the view w.r.t. the window.
- mContentContainer.setX(x);
- mContentContainer.setY(y);
- }
-
- /**
- * Performs the "grow and fade in from the bottom" animation on the floating popup.
- */
- private void growFadeInFromBottom() {
- setPivot();
- mGrowFadeInFromBottomAnimation.start();
- }
-
- /**
- * Performs the "shrink and fade out from bottom" animation on the floating popup.
- */
- private void shrinkFadeOutFromBottom() {
- setPivot();
- mShrinkFadeOutFromBottomAnimation.start();
- }
-
- /**
- * Sets the popup content container's pivot.
- */
- private void setPivot() {
- mContentContainer.setPivotX(mContentContainer.getMeasuredWidth() / 2);
- mContentContainer.setPivotY(mContentContainer.getMeasuredHeight());
- }
-
- private static ViewGroup createContentContainer(Context context) {
- return (ViewGroup) LayoutInflater.from(context)
- .inflate(R.layout.floating_popup_container, null);
- }
-
- private static PopupWindow createPopupWindow(View content) {
- ViewGroup popupContentHolder = new LinearLayout(content.getContext());
- PopupWindow popupWindow = new PopupWindow(popupContentHolder);
- popupWindow.setAnimationStyle(0);
- popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- popupWindow.setWidth(getScreenWidth(content.getContext()));
- popupWindow.setHeight(getScreenHeight(content.getContext()));
- content.setLayoutParams(new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
- popupContentHolder.addView(content);
- return popupWindow;
- }
-
- /**
- * Creates a "grow and fade in from the bottom" animation for the specified view.
- *
- * @param view The view to animate
- */
- private static AnimatorSet createGrowFadeInFromBottom(View view) {
- AnimatorSet growFadeInFromBottomAnimation = new AnimatorSet();
- growFadeInFromBottomAnimation.playTogether(
- ObjectAnimator.ofFloat(view, View.SCALE_X, 0.5f, 1).setDuration(125),
- ObjectAnimator.ofFloat(view, View.SCALE_Y, 0.5f, 1).setDuration(125),
- ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(75));
- return growFadeInFromBottomAnimation;
- }
-
- /**
- * Creates a "shrink and fade out from bottom" animation for the specified view.
- *
- * @param view The view to animate
- * @param listener The animation listener
- */
- private static AnimatorSet createShrinkFadeOutFromBottomAnimation(
- View view, Animator.AnimatorListener listener) {
- AnimatorSet shrinkFadeOutFromBottomAnimation = new AnimatorSet();
- shrinkFadeOutFromBottomAnimation.playTogether(
- ObjectAnimator.ofFloat(view, View.SCALE_Y, 1, 0.5f).setDuration(125),
- ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(75));
- shrinkFadeOutFromBottomAnimation.setStartDelay(150);
- shrinkFadeOutFromBottomAnimation.addListener(listener);
- return shrinkFadeOutFromBottomAnimation;
- }
-
- /**
- * Returns value, restricted to the range min->max (inclusive).
- * If maximum is less than minimum, the result is undefined.
- *
- * @param value The value to clamp.
- * @param minimum The minimum value in the range.
- * @param maximum The maximum value in the range. Must not be less than minimum.
- */
- private static int clamp(int value, int minimum, int maximum) {
- return Math.max(minimum, Math.min(value, maximum));
- }
+ private static int clamp(int value, int minimum, int maximum) {
+ return Math.max(minimum, Math.min(value, maximum));
}
}
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 526885f..4c4a39d 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -543,6 +543,11 @@
return ::lseek64(descriptor, 0, SEEK_CUR) != -1 ? JNI_TRUE : JNI_FALSE;
}
+jobject decodeBitmap(JNIEnv* env, void* data, size_t size) {
+ SkMemoryStream stream(data, size);
+ return doDecode(env, &stream, NULL, NULL);
+}
+
///////////////////////////////////////////////////////////////////////////////
static JNINativeMethod gMethods[] = {
diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h
index a54da43..22a955f 100644
--- a/core/jni/android/graphics/BitmapFactory.h
+++ b/core/jni/android/graphics/BitmapFactory.h
@@ -21,4 +21,6 @@
jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format);
+jobject decodeBitmap(JNIEnv* env, void* data, size_t size);
+
#endif // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_
diff --git a/core/jni/android_emoji_EmojiFactory.cpp b/core/jni/android_emoji_EmojiFactory.cpp
index 655b400..e9f18a6 100644
--- a/core/jni/android_emoji_EmojiFactory.cpp
+++ b/core/jni/android_emoji_EmojiFactory.cpp
@@ -5,6 +5,7 @@
#include <utils/Log.h>
#include <ScopedUtfChars.h>
+#include "BitmapFactory.h"
#include "EmojiFactory.h"
#include "GraphicsJNI.h"
#include <nativehelper/JNIHelp.h>
@@ -164,14 +165,7 @@
return NULL;
}
- SkBitmap *bitmap = new SkBitmap;
- if (!SkImageDecoder::DecodeMemory(bytes, size, bitmap)) {
- ALOGE("SkImageDecoder::DecodeMemory() failed.");
- return NULL;
- }
-
- return GraphicsJNI::createBitmap(env, bitmap,
- GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL);
+ return decodeBitmap(env, (void*)bytes, size);
}
static void android_emoji_EmojiFactory_destructor(
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index fb91c8f..9cf6a9d 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -48,11 +48,6 @@
return NULL;
}
- ScopedLocalRef<jstring> uniqueIdObj(env, env->NewStringUTF(deviceInfo.getIdentifier().uniqueId));
- if (!uniqueIdObj.get()) {
- return NULL;
- }
-
ScopedLocalRef<jobject> kcmObj(env,
android_view_KeyCharacterMap_create(env, deviceInfo.getId(),
deviceInfo.getKeyCharacterMap()));
@@ -62,13 +57,16 @@
const InputDeviceIdentifier& ident = deviceInfo.getIdentifier();
+ // Not sure why, but JNI is complaining when I pass this through directly.
+ jboolean hasMic = deviceInfo.hasMic() ? JNI_TRUE : JNI_FALSE;
+
ScopedLocalRef<jobject> inputDeviceObj(env, env->NewObject(gInputDeviceClassInfo.clazz,
gInputDeviceClassInfo.ctor, deviceInfo.getId(), deviceInfo.getGeneration(),
deviceInfo.getControllerNumber(), nameObj.get(),
static_cast<int32_t>(ident.vendor), static_cast<int32_t>(ident.product),
- uniqueIdObj.get(), descriptorObj.get(), deviceInfo.isExternal(),
- deviceInfo.getSources(), deviceInfo.getKeyboardType(), kcmObj.get(),
- deviceInfo.hasVibrator(), deviceInfo.hasButtonUnderPad()));
+ descriptorObj.get(), deviceInfo.isExternal(), deviceInfo.getSources(),
+ deviceInfo.getKeyboardType(), kcmObj.get(), deviceInfo.hasVibrator(),
+ hasMic, deviceInfo.hasButtonUnderPad()));
const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
for (size_t i = 0; i < ranges.size(); i++) {
@@ -90,7 +88,7 @@
gInputDeviceClassInfo.clazz = MakeGlobalRefOrDie(env, gInputDeviceClassInfo.clazz);
gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
- "(IIILjava/lang/String;IILjava/lang/String;Ljava/lang/String;ZIILandroid/view/KeyCharacterMap;ZZ)V");
+ "(IIILjava/lang/String;IILjava/lang/String;ZIILandroid/view/KeyCharacterMap;ZZZ)V");
gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz,
"addMotionRange", "(IIFFFFF)V");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f427f2b..453cb74 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -56,6 +56,8 @@
<protected-broadcast android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<protected-broadcast android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
<protected-broadcast android:name="android.intent.action.ACTION_SHUTDOWN" />
+ <protected-broadcast android:name="android.intent.action.CHARGING" />
+ <protected-broadcast android:name="android.intent.action.DISCHARGING" />
<protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_LOW" />
<protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_OK" />
<protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_FULL" />
@@ -307,6 +309,8 @@
<protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
+ <protected-broadcast android:name="android.service.persistentdata.action.WIPE_IF_ALLOWED" />
+
<!-- ====================================== -->
<!-- Permissions for things that cost money -->
<!-- ====================================== -->
@@ -3017,6 +3021,12 @@
android:description="@string/permdesc_accessVoiceInteractionService"
android:label="@string/permlab_accessVoiceInteractionService" />
+ <!-- Allows an app that has this permission and a permissions to install packages
+ to request all runtime permissions to be granted at installation.
+ @hide -->
+ <permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS"
+ android:protectionLevel="signature" />
+
<!-- The system process is explicitly the only one allowed to launch the
confirmation UI for full backup/restore -->
<uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
diff --git a/core/res/res/drawable/fastscroll_label_left_material.xml b/core/res/res/drawable/fastscroll_label_left_material.xml
index 430d1b0..c825f73 100644
--- a/core/res/res/drawable/fastscroll_label_left_material.xml
+++ b/core/res/res/drawable/fastscroll_label_left_material.xml
@@ -14,14 +14,18 @@
limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners
- android:topLeftRadius="44dp"
- android:topRightRadius="44dp"
- android:bottomRightRadius="44dp" />
- <padding
- android:paddingLeft="22dp"
- android:paddingRight="22dp" />
- <solid android:color="?attr/colorControlActivated" />
-</shape>
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="16dp">
+ <shape
+ android:shape="rectangle"
+ android:tint="?attr/colorControlActivated">
+ <corners
+ android:topLeftRadius="44dp"
+ android:topRightRadius="44dp"
+ android:bottomRightRadius="44dp" />
+ <padding
+ android:left="22dp"
+ android:right="22dp" />
+ <solid android:color="@color/white" />
+ </shape>
+</inset>
diff --git a/core/res/res/drawable/fastscroll_label_right_material.xml b/core/res/res/drawable/fastscroll_label_right_material.xml
index 6e61397..94f5fde 100644
--- a/core/res/res/drawable/fastscroll_label_right_material.xml
+++ b/core/res/res/drawable/fastscroll_label_right_material.xml
@@ -14,14 +14,18 @@
limitations under the License.
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners
- android:topLeftRadius="44dp"
- android:topRightRadius="44dp"
- android:bottomLeftRadius="44dp" />
- <padding
- android:paddingLeft="22dp"
- android:paddingRight="22dp" />
- <solid android:color="?attr/colorControlActivated" />
-</shape>
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetRight="16dp">
+ <shape
+ android:shape="rectangle"
+ android:tint="?attr/colorControlActivated">
+ <corners
+ android:topLeftRadius="44dp"
+ android:topRightRadius="44dp"
+ android:bottomLeftRadius="44dp" />
+ <padding
+ android:left="22dp"
+ android:right="22dp" />
+ <solid android:color="@color/white" />
+ </shape>
+</inset>
diff --git a/core/res/res/layout/floating_popup_close_overflow_button.xml b/core/res/res/layout/floating_popup_close_overflow_button.xml
new file mode 100644
index 0000000..a1d2811
--- /dev/null
+++ b/core/res/res/layout/floating_popup_close_overflow_button.xml
@@ -0,0 +1,24 @@
+<!--
+/* Copyright 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.
+*/
+-->
+<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/floating_toolbar_menu_button_minimum_width"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/floating_toolbar_menu_button_minimum_width"
+ android:minHeight="@dimen/floating_toolbar_height"
+ android:src="?android:attr/actionModeCloseDrawable"
+ android:contentDescription="@string/floating_toolbar_close_overflow_description"
+ android:background="?attr/selectableItemBackgroundBorderless" />
diff --git a/core/res/res/layout/floating_popup_open_overflow_button.xml b/core/res/res/layout/floating_popup_open_overflow_button.xml
index 4c1176c..dca5384 100644
--- a/core/res/res/layout/floating_popup_open_overflow_button.xml
+++ b/core/res/res/layout/floating_popup_open_overflow_button.xml
@@ -21,5 +21,5 @@
android:minWidth="@dimen/floating_toolbar_menu_button_minimum_width"
android:minHeight="@dimen/floating_toolbar_height"
android:src="@drawable/ic_menu_moreoverflow_material"
- android:contentDescription="@string/action_menu_overflow_description"
+ android:contentDescription="@string/floating_toolbar_open_overflow_description"
android:background="?attr/selectableItemBackgroundBorderless" />
diff --git a/core/res/res/layout/floating_popup_overflow_list_item b/core/res/res/layout/floating_popup_overflow_list_item
new file mode 100644
index 0000000..9294f3b
--- /dev/null
+++ b/core/res/res/layout/floating_popup_overflow_list_item
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 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.
+*/
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceListItemSmall"
+ android:gravity="center_vertical"
+ android:minWidth="@dimen/floating_toolbar_menu_button_side_padding"
+ android:minHeight="@dimen/floating_toolbar_height"
+ android:paddingLeft="@dimen/floating_toolbar_menu_button_side_padding"
+ android:paddingRight="@dimen/floating_toolbar_menu_button_side_padding"
+ android:paddingTop="0dp"
+ android:paddingBottom="0dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:fontFamily="sans-serif"
+ android:textSize="@dimen/floating_toolbar_text_size"
+ android:textAllCaps="true" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index eb37619..aefe79d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3414,9 +3414,9 @@
<attr name="position">
<!-- Floating at the top of the content. -->
<enum name="floating" value="0" />
- <!-- Pinned alongside the thumb. -->
+ <!-- Pinned to the thumb, vertically centered with the middle of the thumb. -->
<enum name="atThumb" value="1" />
- <!-- Pinned above the thumb. -->
+ <!-- Pinned to the thumb, vertically centered with the top edge of the thumb. -->
<enum name="aboveThumb" value="2" />
</attr>
<attr name="textAppearance" />
@@ -3428,6 +3428,16 @@
<attr name="minHeight" />
<!-- Padding for the section header preview. -->
<attr name="padding" />
+ <!-- Position of thumb in relation to the track. -->
+ <attr name="thumbPosition">
+ <!-- The thumb's midpoint is anchored to the track. At its
+ extremes, the thumb will extend half-way outside the
+ track. -->
+ <enum name="midpoint" value="0" />
+ <!-- The thumb is entirely inside the track. At its extremes,
+ the thumb will be contained entirely within the track. -->
+ <enum name="inside" value="1" />
+ </attr>
</declare-styleable>
<declare-styleable name="FrameLayout">
<!-- Determines whether to measure all children or just those in
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e879244..6d9bbae 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2044,15 +2044,11 @@
<!-- Enabled built-in zen mode condition providers -->
<string-array translatable="false" name="config_system_condition_providers">
<item>countdown</item>
- <item>downtime</item>
- <item>next_alarm</item>
+ <item>schedule</item>
</string-array>
- <!-- Show the next-alarm as a zen exit condition if it occurs in the next n hours. -->
- <integer name="config_next_alarm_condition_lookahead_threshold_hrs">12</integer>
-
- <!-- Show downtime as a zen exit condition if it starts in the next n hours. -->
- <integer name="config_downtime_condition_lookahead_threshold_hrs">4</integer>
+ <!-- Priority repeat caller threshold, in minutes -->
+ <integer name="config_zen_repeat_callers_threshold">15</integer>
<!-- Flags enabling default window features. See Window.java -->
<bool name="config_defaultWindowFeatureOptionsPanel">true</bool>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 7d08e7f..2654a25 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -379,11 +379,11 @@
<dimen name="text_handle_min_size">40dp</dimen>
<!-- Lighting and shadow properties -->
- <dimen name="light_y">-200dp</dimen>
- <dimen name="light_z">800dp</dimen>
- <dimen name="light_radius">600dp</dimen>
- <item type="dimen" format="float" name="ambient_shadow_alpha">0.075</item>
- <item type="dimen" format="float" name="spot_shadow_alpha">0.15</item>
+ <dimen name="light_y">0dp</dimen>
+ <dimen name="light_z">600dp</dimen>
+ <dimen name="light_radius">800dp</dimen>
+ <item type="dimen" format="float" name="ambient_shadow_alpha">0.039</item>
+ <item type="dimen" format="float" name="spot_shadow_alpha">0.19</item>
<!-- Floating toolbar dimensions -->
<dimen name="floating_toolbar_height">48dp</dimen>
@@ -392,4 +392,6 @@
<dimen name="floating_toolbar_menu_button_minimum_width">48dp</dimen>
<dimen name="floating_toolbar_default_width">250dp</dimen>
<dimen name="floating_toolbar_minimum_overflow_height">192dp</dimen>
+ <dimen name="floating_toolbar_overflow_width">130dp</dimen>
+ <dimen name="floating_toolbar_margin">2dp</dimen>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 24d17a4..7349d23 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2659,4 +2659,5 @@
<public type="attr" name="breakStrategy" />
<public type="attr" name="supportsAssistGesture" />
+ <public type="attr" name="thumbPosition" />
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7dc3ff7..578aa45 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5170,12 +5170,6 @@
<!-- [CHAR_LIMIT=NONE] Battery saver: Feature description -->
<string name="battery_saver_description">To help improve battery life, battery saver reduces your device’s performance and limits vibration, location services, and most background data. Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery saver turns off automatically when your device is charging.</string>
- <!-- [CHAR_LIMIT=NONE] Zen mode: Condition summary for built-in downtime condition, if active -->
- <string name="downtime_condition_summary">Until your downtime ends at <xliff:g id="formattedTime" example="10:00 PM">%1$s</xliff:g></string>
-
- <!-- [CHAR_LIMIT=NONE] Zen mode: Condition line one for built-in downtime condition, if active -->
- <string name="downtime_condition_line_one">Until your downtime ends</string>
-
<!-- Zen mode condition - summary: time duration in minutes. [CHAR LIMIT=NONE] -->
<plurals name="zen_mode_duration_minutes_summary">
<item quantity="one">For one minute (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
@@ -5206,14 +5200,23 @@
<!-- Zen mode condition: no exit criteria. [CHAR LIMIT=NONE] -->
<string name="zen_mode_forever">Until you turn this off</string>
+ <!-- Zen mode active automatic rule name separator. [CHAR LIMIT=NONE] -->
+ <string name="zen_mode_rule_name_combination"><xliff:g id="first" example="Weeknights">%1$s</xliff:g> / <xliff:g id="rest" example="Meetings">%2$s</xliff:g></string>
+
<!-- Content description for the Toolbar icon used to collapse an expanded action mode. [CHAR LIMIT=NONE] -->
<string name="toolbar_collapse_description">Collapse</string>
- <!-- Zen mode condition - summary: until next alarm. [CHAR LIMIT=NONE] -->
- <string name="zen_mode_next_alarm_summary">Until next alarm at <xliff:g id="formattedTime" example="7:30 AM">%1$s</xliff:g></string>
+ <!-- Zen mode - feature name. [CHAR LIMIT=40] -->
+ <string name="zen_mode_feature_name">Block interruptions</string>
- <!-- Zen mode condition - line one: until next alarm. [CHAR LIMIT=NONE] -->
- <string name="zen_mode_next_alarm_line_one">Until next alarm</string>
+ <!-- Zen mode - downtime legacy feature name. [CHAR LIMIT=40] -->
+ <string name="zen_mode_downtime_feature_name">Downtime</string>
+
+ <!-- Zen mode - name of default automatic schedule for weeknights. [CHAR LIMIT=40] -->
+ <string name="zen_mode_default_weeknights_name">Weeknights</string>
+
+ <!-- Zen mode - name of default automatic schedule for weekends. [CHAR LIMIT=40] -->
+ <string name="zen_mode_default_weekends_name">Weekends</string>
<!-- Indication that the current volume and other effects (vibration) are being suppressed by a third party, such as a notification listener. [CHAR LIMIT=30] -->
<string name="muted_by">Muted by <xliff:g id="third_party">%1$s</xliff:g></string>
@@ -5232,9 +5235,17 @@
<string name="stk_cc_ss_to_ussd">SS request is modified to USSD request.</string>
<string name="stk_cc_ss_to_ss">SS request is modified to new SS request.</string>
+ <!-- User visible name for USB MIDI Peripheral port -->
+ <string name="usb_midi_peripheral_name">Android USB Peripheral Port</string>
<!-- Manufacturer name for USB MIDI Peripheral port -->
<string name="usb_midi_peripheral_manufacturer_name">Android</string>
- <!-- Model name for USB MIDI Peripheral port -->
- <string name="usb_midi_peripheral_model_name">USB Peripheral Port</string>
+ <!-- Product name for USB MIDI Peripheral port -->
+ <string name="usb_midi_peripheral_product_name">USB Peripheral Port</string>
+
+ <!-- Floating toolbar strings -->
+ <!-- Content description for the button that opens the floating toolbar overflow. [CHAR LIMIT=NONE] -->
+ <string name="floating_toolbar_open_overflow_description">More options</string>
+ <!-- Content description for the button that closes the floating toolbar overflow. [CHAR LIMIT=NONE] -->
+ <string name="floating_toolbar_close_overflow_description">Close overflow</string>
</resources>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 9cf7884..7cb8144 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -943,9 +943,10 @@
<item name="thumbMinWidth">0dp</item>
<item name="thumbMinHeight">0dp</item>
<item name="textSize">45sp</item>
- <item name="minWidth">88dp</item>
+ <item name="minWidth">104dp</item>
<item name="minHeight">88dp</item>
<item name="padding">0dp</item>
+ <item name="thumbPosition">inside</item>
</style>
<style name="Widget.Material.PreferenceFrameLayout">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b4ba316..42d187d 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2037,19 +2037,18 @@
<java-symbol type="dimen" name="timepicker_text_size_normal" />
<java-symbol type="dimen" name="timepicker_text_size_inner" />
<java-symbol type="string" name="battery_saver_description" />
- <java-symbol type="string" name="downtime_condition_summary" />
- <java-symbol type="string" name="downtime_condition_line_one" />
<java-symbol type="string" name="zen_mode_forever" />
+ <java-symbol type="string" name="zen_mode_rule_name_combination" />
<java-symbol type="plurals" name="zen_mode_duration_minutes" />
<java-symbol type="plurals" name="zen_mode_duration_hours" />
<java-symbol type="plurals" name="zen_mode_duration_minutes_summary" />
<java-symbol type="plurals" name="zen_mode_duration_hours_summary" />
<java-symbol type="string" name="zen_mode_until" />
- <java-symbol type="string" name="zen_mode_next_alarm_summary" />
- <java-symbol type="string" name="zen_mode_next_alarm_line_one" />
+ <java-symbol type="string" name="zen_mode_feature_name" />
+ <java-symbol type="string" name="zen_mode_downtime_feature_name" />
+ <java-symbol type="string" name="zen_mode_default_weeknights_name" />
+ <java-symbol type="string" name="zen_mode_default_weekends_name" />
<java-symbol type="array" name="config_system_condition_providers" />
- <java-symbol type="integer" name="config_next_alarm_condition_lookahead_threshold_hrs" />
- <java-symbol type="integer" name="config_downtime_condition_lookahead_threshold_hrs" />
<java-symbol type="string" name="muted_by" />
<java-symbol type="string" name="select_day" />
@@ -2182,8 +2181,9 @@
<java-symbol type="bool" name="config_LTE_eri_for_network_name" />
<java-symbol type="bool" name="config_defaultInTouchMode" />
+ <java-symbol type="string" name="usb_midi_peripheral_name" />
<java-symbol type="string" name="usb_midi_peripheral_manufacturer_name" />
- <java-symbol type="string" name="usb_midi_peripheral_model_name" />
+ <java-symbol type="string" name="usb_midi_peripheral_product_name" />
<java-symbol type="bool" name="allow_stacked_button_bar" />
<java-symbol type="id" name="spacer" />
@@ -2214,12 +2214,16 @@
<java-symbol type="layout" name="floating_popup_container" />
<java-symbol type="layout" name="floating_popup_menu_button" />
<java-symbol type="layout" name="floating_popup_open_overflow_button" />
+ <java-symbol type="layout" name="floating_popup_close_overflow_button" />
+ <java-symbol type="layout" name="floating_popup_overflow_list_item" />
<java-symbol type="dimen" name="floating_toolbar_height" />
<java-symbol type="dimen" name="floating_toolbar_menu_button_side_padding" />
<java-symbol type="dimen" name="floating_toolbar_text_size" />
<java-symbol type="dimen" name="floating_toolbar_menu_button_minimum_width" />
<java-symbol type="dimen" name="floating_toolbar_default_width" />
<java-symbol type="dimen" name="floating_toolbar_minimum_overflow_height" />
+ <java-symbol type="dimen" name="floating_toolbar_overflow_width" />
+ <java-symbol type="dimen" name="floating_toolbar_margin" />
<java-symbol type="drawable" name="ic_chevron_left" />
<java-symbol type="drawable" name="ic_chevron_right" />
@@ -2227,4 +2231,5 @@
<java-symbol type="string" name="date_picker_next_month_button" />
<java-symbol type="layout" name="date_picker_month_item_material" />
<java-symbol type="id" name="month_view" />
+ <java-symbol type="integer" name="config_zen_repeat_callers_threshold" />
</resources>
diff --git a/core/res/res/xml/default_zen_mode_config.xml b/core/res/res/xml/default_zen_mode_config.xml
index 1bdc1ec..5f4199a 100644
--- a/core/res/res/xml/default_zen_mode_config.xml
+++ b/core/res/res/xml/default_zen_mode_config.xml
@@ -18,7 +18,6 @@
-->
<!-- Default configuration for zen mode. See android.service.notification.ZenModeConfig. -->
-<zen version="1">
- <allow calls="false" messages="false" />
- <sleep startHour="22" startMin="0" endHour="7" endMin="0" />
+<zen version="2">
+ <allow calls="true" messages="false" reminders="true" events="true" />
</zen>
diff --git a/docs/html/google/play-services/setup.jd b/docs/html/google/play-services/setup.jd
index e75235e..70e7107 100644
--- a/docs/html/google/play-services/setup.jd
+++ b/docs/html/google/play-services/setup.jd
@@ -9,7 +9,7 @@
<h2>In this document</h2>
<ol>
<li><a href="#Setup">Add Google Play Services to Your Project</a></li>
- <li><a href="#Proguard">Create a Proguard Exception</a></li>
+ <li><a href="#Proguard">Create a ProGuard Exception</a></li>
<li><a href="#ensure">Ensure Devices Have the Google Play services APK</a></li>
</ol>
@@ -195,6 +195,17 @@
</tr>
</table>
+<p class="note"><strong>Note:</strong> ProGuard directives are included in the Play services
+client libraries to preserve the required classes. The
+<a href="{@docRoot}tools/building/plugin-for-gradle.html">Android Plugin for Gradle</a>
+automatically appends ProGuard configuration files in an AAR (Android ARchive) package and appends
+that package to your ProGuard configuration. During project creation, Android Studio automatically
+creates the ProGuard configuration files and <code>build.gradle</code> properties for ProGuard use.
+To use ProGuard with Android Studio, you must enable the ProGuard setting in your
+<code>build.gradle</code> <code>buildTypes</code>. For more information, see the
+<a href="{@docRoot}tools/help/proguard.html">ProGuard</a> topic. </p>
+
+
</div><!-- end studio -->
<div class="select-ide eclipse">
@@ -230,6 +241,33 @@
you can begin developing features with the
<a href="{@docRoot}reference/gms-packages.html">Google Play services APIs</a>.</p>
+
+<h2 id="Proguard">Create a ProGuard Exception</h2>
+
+<p>To prevent <a href="{@docRoot}tools/help/proguard.html">ProGuard</a> from stripping away
+required classes, add the following lines in the
+<code><project_directory>/proguard-project.txt</code> file:
+<pre>
+-keep class * extends java.util.ListResourceBundle {
+ protected Object[][] getContents();
+}
+
+-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
+ public static final *** NULL;
+}
+
+-keepnames @com.google.android.gms.common.annotation.KeepName class *
+-keepclassmembernames class * {
+ @com.google.android.gms.common.annotation.KeepName *;
+}
+
+-keepnames class * implements android.os.Parcelable {
+ public static final ** CREATOR;
+}
+</pre>
+
+
+
</div><!-- end eclipse -->
<div class="select-ide other">
@@ -263,8 +301,6 @@
you can begin developing features with the
<a href="{@docRoot}reference/gms-packages.html">Google Play services APIs</a>.</p>
-</div><!-- end other -->
-
<h2 id="Proguard">Create a Proguard Exception</h2>
@@ -290,11 +326,9 @@
}
</pre>
-<p class="note"><strong>Note:</strong> When using Android Studio, you must add Proguard
-to your <code>build.gradle</code> file's build types. For more information, see the
-<a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Running-ProGuard"
->Gradle Plugin User Guide</a>.
-</ol>
+
+</div><!-- end other -->
+
<h2 id="ensure">Ensure Devices Have the Google Play services APK</h2>
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index a2f71e5..05a81de 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -163,7 +163,6 @@
inflateLayers(r, parser, attrs, theme);
ensurePadding();
- onStateChange(getState());
}
/**
@@ -211,7 +210,11 @@
updateLayerFromTypedArray(layer, a);
a.recycle();
- if (layer.mDrawable == null) {
+ // If the layer doesn't have a drawable or unresolved theme
+ // attribute for a drawable, attempt to parse one from the child
+ // element.
+ if (layer.mDrawable == null && (layer.mThemeAttrs == null ||
+ layer.mThemeAttrs[R.styleable.LayerDrawableItem_drawable] == 0)) {
while ((type = parser.next()) == XmlPullParser.TEXT) {
}
if (type != XmlPullParser.START_TAG) {
@@ -303,7 +306,6 @@
}
ensurePadding();
- onStateChange(getState());
}
@Override
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index a5776a4..da70e9b 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -112,7 +112,7 @@
*
* The PNG chunk type is "npTc".
*/
-struct Res_png_9patch
+struct alignas(uintptr_t) Res_png_9patch
{
Res_png_9patch() : wasDeserialized(false), xDivsOffset(0),
yDivsOffset(0), colorsOffset(0) { }
diff --git a/keystore/java/android/security/AndroidKeyStore.java b/keystore/java/android/security/AndroidKeyStore.java
index ed690de..c5b6a68 100644
--- a/keystore/java/android/security/AndroidKeyStore.java
+++ b/keystore/java/android/security/AndroidKeyStore.java
@@ -512,12 +512,23 @@
}
}
- int purposes = params.getPurposes();
+ @KeyStoreKeyConstraints.PurposeEnum int purposes = params.getPurposes();
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes = params.getBlockModes();
+ if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
+ && (params.isRandomizedEncryptionRequired())) {
+ @KeyStoreKeyConstraints.BlockModeEnum int incompatibleBlockModes =
+ blockModes & ~KeyStoreKeyConstraints.BlockMode.IND_CPA_COMPATIBLE_MODES;
+ if (incompatibleBlockModes != 0) {
+ throw new KeyStoreException("Randomized encryption (IND-CPA) required but may be"
+ + " violated by block mode(s): "
+ + KeyStoreKeyConstraints.BlockMode.allToString(incompatibleBlockModes)
+ + ". See KeyStoreParameter documentation.");
+ }
+ }
for (int keymasterPurpose : KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
}
- for (int keymasterBlockMode :
- KeyStoreKeyConstraints.BlockMode.allToKeymaster(params.getBlockModes())) {
+ for (int keymasterBlockMode : KeyStoreKeyConstraints.BlockMode.allToKeymaster(blockModes)) {
args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockMode);
}
for (int keymasterPadding :
@@ -553,8 +564,8 @@
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keyMaterial.length * 8);
if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
- || ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) {
- // Permit caller-specified IV. This is needed for the Cipher abstraction.
+ && (!params.isRandomizedEncryptionRequired())) {
+ // Permit caller-provided IV when encrypting with this key
args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
}
diff --git a/keystore/java/android/security/AndroidKeyStoreProvider.java b/keystore/java/android/security/AndroidKeyStoreProvider.java
index a59927d..43f3b30 100644
--- a/keystore/java/android/security/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/AndroidKeyStoreProvider.java
@@ -49,14 +49,26 @@
// javax.crypto.KeyGenerator
put("KeyGenerator.AES", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$AES");
+ put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA1");
+ put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA224");
put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA256");
+ put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA384");
+ put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".KeyStoreKeyGeneratorSpi$HmacSHA512");
// java.security.SecretKeyFactory
- put("SecretKeyFactory.AES", PACKAGE_NAME + ".KeyStoreSecretKeyFactorySpi");
- put("SecretKeyFactory.HmacSHA256", PACKAGE_NAME + ".KeyStoreSecretKeyFactorySpi");
+ putSecretKeyFactoryImpl("AES");
+ putSecretKeyFactoryImpl("HmacSHA1");
+ putSecretKeyFactoryImpl("HmacSHA224");
+ putSecretKeyFactoryImpl("HmacSHA256");
+ putSecretKeyFactoryImpl("HmacSHA384");
+ putSecretKeyFactoryImpl("HmacSHA512");
// javax.crypto.Mac
+ putMacImpl("HmacSHA1", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA1");
+ putMacImpl("HmacSHA224", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA224");
putMacImpl("HmacSHA256", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA256");
+ putMacImpl("HmacSHA384", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA384");
+ putMacImpl("HmacSHA512", PACKAGE_NAME + ".KeyStoreHmacSpi$HmacSHA512");
// javax.crypto.Cipher
putSymmetricCipherImpl("AES/ECB/NoPadding",
@@ -73,6 +85,10 @@
PACKAGE_NAME + ".KeyStoreCipherSpi$AES$CTR$NoPadding");
}
+ private void putSecretKeyFactoryImpl(String algorithm) {
+ put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".KeyStoreSecretKeyFactorySpi");
+ }
+
private void putMacImpl(String algorithm, String implClass) {
put("Mac." + algorithm, implClass);
put("Mac." + algorithm + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME);
diff --git a/keystore/java/android/security/KeyGeneratorSpec.java b/keystore/java/android/security/KeyGeneratorSpec.java
index 0e490cd..4eedb24 100644
--- a/keystore/java/android/security/KeyGeneratorSpec.java
+++ b/keystore/java/android/security/KeyGeneratorSpec.java
@@ -22,6 +22,7 @@
import java.security.spec.AlgorithmParameterSpec;
import java.util.Date;
+import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@@ -51,6 +52,7 @@
private final @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final boolean mRandomizedEncryptionRequired;
private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private final int mUserAuthenticationValidityDurationSeconds;
private final boolean mInvalidatedOnNewFingerprintEnrolled;
@@ -66,6 +68,7 @@
@KeyStoreKeyConstraints.PurposeEnum int purposes,
@KeyStoreKeyConstraints.PaddingEnum int paddings,
@KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ boolean randomizedEncryptionRequired,
@KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
int userAuthenticationValidityDurationSeconds,
boolean invalidatedOnNewFingerprintEnrolled) {
@@ -89,6 +92,7 @@
mPurposes = purposes;
mPaddings = paddings;
mBlockModes = blockModes;
+ mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticators = userAuthenticators;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
@@ -172,6 +176,19 @@
}
/**
+ * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic property
+ * being required is <em>indistinguishability under chosen-plaintext attack ({@code
+ * IND-CPA})</em>. This property is important because it mitigates several classes of
+ * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
+ * given plaintext always produces the same ciphertext, an attacker may see the repeated
+ * ciphertexts and be able to deduce something about the plaintext.
+ */
+ public boolean isRandomizedEncryptionRequired() {
+ return mRandomizedEncryptionRequired;
+ }
+
+ /**
* Gets the set of user authenticators which protect access to this key. The key can only be
* used iff the user has authenticated to at least one of these user authenticators.
*
@@ -223,6 +240,7 @@
private @KeyStoreKeyConstraints.PurposeEnum int mPurposes;
private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private boolean mRandomizedEncryptionRequired = true;
private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private int mUserAuthenticationValidityDurationSeconds = -1;
private boolean mInvalidatedOnNewFingerprintEnrolled;
@@ -281,7 +299,7 @@
/**
* Sets the time instant before which the key is not yet valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityEnd(Date)
*/
@@ -293,7 +311,7 @@
/**
* Sets the time instant after which the key is no longer valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityStart(Date)
* @see #setKeyValidityForConsumptionEnd(Date)
@@ -308,7 +326,7 @@
/**
* Sets the time instant after which the key is no longer valid for encryption and signing.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForConsumptionEnd(Date)
*/
@@ -321,7 +339,7 @@
* Sets the time instant after which the key is no longer valid for decryption and
* verification.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForOriginationEnd(Date)
*/
@@ -363,6 +381,43 @@
}
/**
+ * Sets whether encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic
+ * property being required is <em>indistinguishability under chosen-plaintext attack
+ * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
+ * of weaknesses due to which ciphertext may leak information about plaintext. For example,
+ * if a given plaintext always produces the same ciphertext, an attacker may see the
+ * repeated ciphertexts and be able to deduce something about the plaintext.
+ *
+ * <p>By default, {@code IND-CPA} is required.
+ *
+ * <p>When {@code IND-CPA} is required:
+ * <ul>
+ * <li>block modes which do not offer {@code IND-CPA}, such as {@code ECB}, are prohibited;
+ * </li>
+ * <li>in block modes which use an IV, such as {@code CBC}, {@code CTR}, and {@code GCM},
+ * caller-provided IVs are rejected when encrypting, to ensure that only random IVs are
+ * used.</li>
+ *
+ * <p>Before disabling this requirement, consider the following approaches instead:
+ * <ul>
+ * <li>If you are generating a random IV for encryption and then initializing a {@code}
+ * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV
+ * instead. This will occur if the {@code Cipher} is initialized for encryption without an
+ * IV. The IV can then be queried via {@link Cipher#getIV()}.</li>
+ * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully
+ * random, such as the name of the file being encrypted, or transaction ID, or password,
+ * or a device identifier), consider changing your design to use a random IV which will then
+ * be provided in addition to the ciphertext to the entities which need to decrypt the
+ * ciphertext.</li>
+ * </ul>
+ */
+ public Builder setRandomizedEncryptionRequired(boolean required) {
+ mRandomizedEncryptionRequired = required;
+ return this;
+ }
+
+ /**
* Sets the user authenticators which protect access to this key. The key can only be used
* iff the user has authenticated to at least one of these user authenticators.
*
@@ -427,6 +482,7 @@
mPurposes,
mPaddings,
mBlockModes,
+ mRandomizedEncryptionRequired,
mUserAuthenticators,
mUserAuthenticationValidityDurationSeconds,
mInvalidatedOnNewFingerprintEnrolled);
diff --git a/keystore/java/android/security/KeyPairGeneratorSpec.java b/keystore/java/android/security/KeyPairGeneratorSpec.java
index 52b7097..4ca220d 100644
--- a/keystore/java/android/security/KeyPairGeneratorSpec.java
+++ b/keystore/java/android/security/KeyPairGeneratorSpec.java
@@ -86,6 +86,8 @@
private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final boolean mRandomizedEncryptionRequired;
+
private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private final int mUserAuthenticationValidityDurationSeconds;
@@ -134,6 +136,7 @@
@KeyStoreKeyConstraints.DigestEnum int digests,
@KeyStoreKeyConstraints.PaddingEnum int paddings,
@KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ boolean randomizedEncryptionRequired,
@KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
int userAuthenticationValidityDurationSeconds,
boolean invalidatedOnNewFingerprintEnrolled) {
@@ -174,6 +177,7 @@
mDigests = digests;
mPaddings = paddings;
mBlockModes = blockModes;
+ mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticators = userAuthenticators;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
@@ -186,8 +190,28 @@
public KeyPairGeneratorSpec(Context context, String keyStoreAlias, String keyType, int keySize,
AlgorithmParameterSpec spec, X500Principal subjectDN, BigInteger serialNumber,
Date startDate, Date endDate, int flags) {
- this(context, keyStoreAlias, keyType, keySize, spec, subjectDN, serialNumber, startDate,
- endDate, flags, startDate, endDate, endDate, 0, 0, 0, 0, 0, -1, false);
+
+ this(context,
+ keyStoreAlias,
+ keyType,
+ keySize,
+ spec,
+ subjectDN,
+ serialNumber,
+ startDate,
+ endDate,
+ flags,
+ startDate,
+ endDate,
+ endDate,
+ 0,
+ 0,
+ 0,
+ 0,
+ true,
+ 0,
+ -1,
+ false);
}
/**
@@ -347,6 +371,21 @@
}
/**
+ * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic property
+ * being required is <em>indistinguishability under chosen-plaintext attack ({@code
+ * IND-CPA})</em>. This property is important because it mitigates several classes of
+ * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
+ * given plaintext always produces the same ciphertext, an attacker may see the repeated
+ * ciphertexts and be able to deduce something about the plaintext.
+ *
+ * @hide
+ */
+ public boolean isRandomizedEncryptionRequired() {
+ return mRandomizedEncryptionRequired;
+ }
+
+ /**
* Gets the set of user authenticators which protect access to the private key. The key can only
* be used iff the user has authenticated to at least one of these user authenticators.
*
@@ -446,6 +485,8 @@
private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private boolean mRandomizedEncryptionRequired = true;
+
private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private int mUserAuthenticationValidityDurationSeconds = -1;
@@ -580,7 +621,7 @@
/**
* Sets the time instant before which the key is not yet valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityEnd(Date)
*
@@ -594,7 +635,7 @@
/**
* Sets the time instant after which the key is no longer valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityStart(Date)
* @see #setKeyValidityForConsumptionEnd(Date)
@@ -611,7 +652,7 @@
/**
* Sets the time instant after which the key is no longer valid for encryption and signing.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForConsumptionEnd(Date)
*
@@ -626,7 +667,7 @@
* Sets the time instant after which the key is no longer valid for decryption and
* verification.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForOriginationEnd(Date)
*
@@ -689,6 +730,33 @@
}
/**
+ * Sets whether encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic
+ * property being required is <em>indistinguishability under chosen-plaintext attack
+ * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
+ * of weaknesses due to which ciphertext may leak information about plaintext. For example,
+ * if a given plaintext always produces the same ciphertext, an attacker may see the
+ * repeated ciphertexts and be able to deduce something about the plaintext.
+ *
+ * <p>By default, {@code IND-CPA} is required.
+ *
+ * <p>When {@code IND-CPA} is required, encryption/decryption transformations which do not
+ * offer {@code IND-CPA}, such as RSA without padding, are prohibited.
+ *
+ * <p>Before disabling this requirement, consider the following approaches instead:
+ * <ul>
+ * <li>If you are using RSA encryption without padding, consider switching to padding
+ * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ public Builder setRandomizedEncryptionRequired(boolean required) {
+ mRandomizedEncryptionRequired = required;
+ return this;
+ }
+
+ /**
* Sets the user authenticators which protect access to this key. The key can only be used
* iff the user has authenticated to at least one of these user authenticators.
*
@@ -771,6 +839,7 @@
mDigests,
mPaddings,
mBlockModes,
+ mRandomizedEncryptionRequired,
mUserAuthenticators,
mUserAuthenticationValidityDurationSeconds,
mInvalidatedOnNewFingerprintEnrolled);
diff --git a/keystore/java/android/security/KeyStoreCipherSpi.java b/keystore/java/android/security/KeyStoreCipherSpi.java
index ec358d6..487eac0 100644
--- a/keystore/java/android/security/KeyStoreCipherSpi.java
+++ b/keystore/java/android/security/KeyStoreCipherSpi.java
@@ -111,7 +111,9 @@
private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockMode;
private final @KeyStoreKeyConstraints.PaddingEnum int mPadding;
private final int mBlockSizeBytes;
- private final boolean mIvUsed;
+
+ /** Whether this transformation requires an IV. */
+ private final boolean mIvRequired;
// Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after
// doFinal finishes.
@@ -119,10 +121,13 @@
private KeyStoreSecretKey mKey;
private SecureRandom mRng;
private boolean mFirstOperationInitiated;
- byte[] mIv;
+ private byte[] mIv;
+ /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */
+ private boolean mIvHasBeenUsed;
- // Fields below must be reset
+ // Fields below must be reset after doFinal
private byte[] mAdditionalEntropyForBegin;
+
/**
* Token referencing this operation inside keystore service. It is initialized by
* {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and one some
@@ -143,7 +148,7 @@
mBlockMode = blockMode;
mPadding = padding;
mBlockSizeBytes = blockSizeBytes;
- mIvUsed = ivUsed;
+ mIvRequired = ivUsed;
}
@Override
@@ -170,7 +175,7 @@
}
private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException {
- reset();
+ resetAll();
if (!(key instanceof KeyStoreSecretKey)) {
throw new InvalidKeyException(
"Unsupported key: " + ((key != null) ? key.getClass().getName() : "null"));
@@ -187,7 +192,25 @@
mEncrypting = opmode == Cipher.ENCRYPT_MODE;
}
- private void reset() {
+ private void resetAll() {
+ IBinder operationToken = mOperationToken;
+ if (operationToken != null) {
+ mOperationToken = null;
+ mKeyStore.abort(operationToken);
+ }
+ mEncrypting = false;
+ mKey = null;
+ mRng = null;
+ mFirstOperationInitiated = false;
+ mIv = null;
+ mIvHasBeenUsed = false;
+ mAdditionalEntropyForBegin = null;
+ mOperationToken = null;
+ mOperationHandle = null;
+ mMainDataStreamer = null;
+ }
+
+ private void resetWhilePreservingInitState() {
IBinder operationToken = mOperationToken;
if (operationToken != null) {
mOperationToken = null;
@@ -205,6 +228,12 @@
if (mKey == null) {
throw new IllegalStateException("Not initialized");
}
+ if ((mEncrypting) && (mIvRequired) && (mIvHasBeenUsed)) {
+ // IV is being reused for encryption: this violates security best practices.
+ throw new IllegalStateException(
+ "IV has already been used. Reusing IV in encryption mode violates security best"
+ + " practices.");
+ }
KeymasterArguments keymasterInputArgs = new KeymasterArguments();
keymasterInputArgs.addInt(KeymasterDefs.KM_TAG_ALGORITHM, mAlgorithm);
@@ -234,6 +263,7 @@
mOperationHandle = opResult.operationHandle;
loadAlgorithmSpecificParametersFromBeginResult(keymasterOutputArgs);
mFirstOperationInitiated = true;
+ mIvHasBeenUsed = true;
mMainDataStreamer = new KeyStoreCryptoOperationChunkedStreamer(
new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(
mKeyStore, opResult.token));
@@ -298,7 +328,7 @@
}
}
- reset();
+ resetWhilePreservingInitState();
return output;
}
@@ -376,7 +406,7 @@
*/
@Override
protected AlgorithmParameters engineGetParameters() {
- if (!mIvUsed) {
+ if (!mIvRequired) {
return null;
}
if ((mIv != null) && (mIv.length > 0)) {
@@ -408,7 +438,7 @@
*/
protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException {
- if (!mIvUsed) {
+ if (!mIvRequired) {
if (params != null) {
throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
}
@@ -447,7 +477,7 @@
*/
protected void initAlgorithmSpecificParameters(AlgorithmParameters params)
throws InvalidAlgorithmParameterException {
- if (!mIvUsed) {
+ if (!mIvRequired) {
if (params != null) {
throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params);
}
@@ -492,7 +522,7 @@
* and thus {@code Cipher.init} needs to be invoked with explicitly provided parameters.
*/
protected void initAlgorithmSpecificParameters() throws InvalidKeyException {
- if (!mIvUsed) {
+ if (!mIvRequired) {
return;
}
@@ -515,7 +545,7 @@
if (!mFirstOperationInitiated) {
// First begin operation -- see if we need to provide additional entropy for IV
// generation.
- if (mIvUsed) {
+ if (mIvRequired) {
// IV is needed
if ((mIv == null) && (mEncrypting)) {
// TODO: Switch to keymaster-generated IV code below once keymaster supports
@@ -534,7 +564,7 @@
}
}
- if ((mIvUsed) && (mIv != null)) {
+ if ((mIvRequired) && (mIv != null)) {
keymasterArgs.addBlob(KeymasterDefs.KM_TAG_NONCE, mIv);
}
}
@@ -557,7 +587,7 @@
returnedIv = null;
}
- if (mIvUsed) {
+ if (mIvRequired) {
if (mIv == null) {
mIv = returnedIv;
} else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) {
diff --git a/keystore/java/android/security/KeyStoreHmacSpi.java b/keystore/java/android/security/KeyStoreHmacSpi.java
index a5864a4..f69e7d1 100644
--- a/keystore/java/android/security/KeyStoreHmacSpi.java
+++ b/keystore/java/android/security/KeyStoreHmacSpi.java
@@ -35,9 +35,33 @@
*/
public abstract class KeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation {
+ public static class HmacSHA1 extends KeyStoreHmacSpi {
+ public HmacSHA1() {
+ super(KeyStoreKeyConstraints.Digest.SHA1);
+ }
+ }
+
+ public static class HmacSHA224 extends KeyStoreHmacSpi {
+ public HmacSHA224() {
+ super(KeyStoreKeyConstraints.Digest.SHA224);
+ }
+ }
+
public static class HmacSHA256 extends KeyStoreHmacSpi {
public HmacSHA256() {
- super(KeyStoreKeyConstraints.Digest.SHA256, 256 / 8);
+ super(KeyStoreKeyConstraints.Digest.SHA256);
+ }
+ }
+
+ public static class HmacSHA384 extends KeyStoreHmacSpi {
+ public HmacSHA384() {
+ super(KeyStoreKeyConstraints.Digest.SHA384);
+ }
+ }
+
+ public static class HmacSHA512 extends KeyStoreHmacSpi {
+ public HmacSHA512() {
+ super(KeyStoreKeyConstraints.Digest.SHA512);
}
}
@@ -52,9 +76,9 @@
private IBinder mOperationToken;
private Long mOperationHandle;
- protected KeyStoreHmacSpi(@KeyStoreKeyConstraints.DigestEnum int digest, int macSizeBytes) {
+ protected KeyStoreHmacSpi(@KeyStoreKeyConstraints.DigestEnum int digest) {
mDigest = digest;
- mMacSizeBytes = macSizeBytes;
+ mMacSizeBytes = KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest);
}
@Override
diff --git a/keystore/java/android/security/KeyStoreKeyCharacteristics.java b/keystore/java/android/security/KeyStoreKeyCharacteristics.java
index 543b5d8..1f5d400 100644
--- a/keystore/java/android/security/KeyStoreKeyCharacteristics.java
+++ b/keystore/java/android/security/KeyStoreKeyCharacteristics.java
@@ -31,7 +31,7 @@
private KeyStoreKeyCharacteristics() {}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({Origin.GENERATED_INSIDE_TEE, Origin.GENERATED_OUTSIDE_OF_TEE, Origin.IMPORTED})
+ @IntDef({Origin.GENERATED, Origin.IMPORTED})
public @interface OriginEnum {}
/**
@@ -40,14 +40,11 @@
public static abstract class Origin {
private Origin() {}
- /** Key was generated inside a TEE. */
- public static final int GENERATED_INSIDE_TEE = 1;
+ /** Key was generated inside AndroidKeyStore. */
+ public static final int GENERATED = 1 << 0;
- /** Key was generated outside of a TEE. */
- public static final int GENERATED_OUTSIDE_OF_TEE = 2;
-
- /** Key was imported. */
- public static final int IMPORTED = 0;
+ /** Key was imported into AndroidKeyStore. */
+ public static final int IMPORTED = 1 << 1;
/**
* @hide
@@ -55,9 +52,7 @@
public static @OriginEnum int fromKeymaster(int origin) {
switch (origin) {
case KeymasterDefs.KM_ORIGIN_HARDWARE:
- return GENERATED_INSIDE_TEE;
- case KeymasterDefs.KM_ORIGIN_SOFTWARE:
- return GENERATED_OUTSIDE_OF_TEE;
+ return GENERATED;
case KeymasterDefs.KM_ORIGIN_IMPORTED:
return IMPORTED;
default:
diff --git a/keystore/java/android/security/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java
index 7137a9a..98ac3e7 100644
--- a/keystore/java/android/security/KeyStoreKeyConstraints.java
+++ b/keystore/java/android/security/KeyStoreKeyConstraints.java
@@ -123,7 +123,7 @@
}
@Retention(RetentionPolicy.SOURCE)
- @IntDef({Algorithm.AES, Algorithm.HMAC})
+ @IntDef({Algorithm.AES, Algorithm.HMAC, Algorithm.RSA, Algorithm.EC})
public @interface AlgorithmEnum {}
/**
@@ -135,12 +135,22 @@
/**
* Key algorithm: AES.
*/
- public static final int AES = 0;
+ public static final int AES = 1 << 0;
/**
* Key algorithm: HMAC.
*/
- public static final int HMAC = 1;
+ public static final int HMAC = 1 << 1;
+
+ /**
+ * Key algorithm: RSA.
+ */
+ public static final int RSA = 1 << 2;
+
+ /**
+ * Key algorithm: EC.
+ */
+ public static final int EC = 1 << 3;
/**
* @hide
@@ -151,6 +161,10 @@
return KeymasterDefs.KM_ALGORITHM_AES;
case HMAC:
return KeymasterDefs.KM_ALGORITHM_HMAC;
+ case RSA:
+ return KeymasterDefs.KM_ALGORITHM_RSA;
+ case EC:
+ return KeymasterDefs.KM_ALGORITHM_EC;
default:
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
}
@@ -165,6 +179,10 @@
return AES;
case KeymasterDefs.KM_ALGORITHM_HMAC:
return HMAC;
+ case KeymasterDefs.KM_ALGORITHM_RSA:
+ return RSA;
+ case KeymasterDefs.KM_ALGORITHM_EC:
+ return EC;
default:
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
}
@@ -179,6 +197,10 @@
return "AES";
case HMAC:
return "HMAC";
+ case RSA:
+ return "RSA";
+ case EC:
+ return "EC";
default:
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
}
@@ -213,8 +235,18 @@
throw new IllegalArgumentException("HMAC digest not specified");
}
switch (digest) {
+ case Digest.MD5:
+ return "HmacMD5";
+ case Digest.SHA1:
+ return "HmacSHA1";
+ case Digest.SHA224:
+ return "HmacSHA224";
case Digest.SHA256:
return "HmacSHA256";
+ case Digest.SHA384:
+ return "HmacSHA384";
+ case Digest.SHA512:
+ return "HmacSHA512";
default:
throw new IllegalArgumentException(
"Unsupported HMAC digest: " + digest);
@@ -223,11 +255,32 @@
throw new IllegalArgumentException("Unsupported key algorithm: " + algorithm);
}
}
+
+ /**
+ * @hide
+ */
+ public static String toJCAKeyPairAlgorithm(@AlgorithmEnum int algorithm) {
+ switch (algorithm) {
+ case RSA:
+ return "RSA";
+ case EC:
+ return "EC";
+ default:
+ throw new IllegalArgumentException("Unsupported key alorithm: " + algorithm);
+ }
+ }
}
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true,
- value = {Padding.NONE, Padding.PKCS7})
+ value = {
+ Padding.NONE,
+ Padding.PKCS7,
+ Padding.RSA_PKCS1_ENCRYPTION,
+ Padding.RSA_PKCS1_SIGNATURE,
+ Padding.RSA_OAEP,
+ Padding.RSA_PSS,
+ })
public @interface PaddingEnum {}
/**
@@ -247,6 +300,26 @@
public static final int PKCS7 = 1 << 1;
/**
+ * RSA PKCS#1 v1.5 padding for encryption/decryption.
+ */
+ public static final int RSA_PKCS1_ENCRYPTION = 1 << 2;
+
+ /**
+ * RSA PKCS#1 v1.5 padding for signatures.
+ */
+ public static final int RSA_PKCS1_SIGNATURE = 1 << 3;
+
+ /**
+ * RSA Optimal Asymmetric Encryption Padding (OAEP).
+ */
+ public static final int RSA_OAEP = 1 << 4;
+
+ /**
+ * RSA PKCS#1 v2.1 Probabilistic Signature Scheme (PSS) padding.
+ */
+ public static final int RSA_PSS = 1 << 5;
+
+ /**
* @hide
*/
public static int toKeymaster(int padding) {
@@ -255,6 +328,14 @@
return KeymasterDefs.KM_PAD_NONE;
case PKCS7:
return KeymasterDefs.KM_PAD_PKCS7;
+ case RSA_PKCS1_ENCRYPTION:
+ return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT;
+ case RSA_PKCS1_SIGNATURE:
+ return KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN;
+ case RSA_OAEP:
+ return KeymasterDefs.KM_PAD_RSA_OAEP;
+ case RSA_PSS:
+ return KeymasterDefs.KM_PAD_RSA_PSS;
default:
throw new IllegalArgumentException("Unknown padding: " + padding);
}
@@ -269,6 +350,14 @@
return NONE;
case KeymasterDefs.KM_PAD_PKCS7:
return PKCS7;
+ case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
+ return RSA_PKCS1_ENCRYPTION;
+ case KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN:
+ return RSA_PKCS1_SIGNATURE;
+ case KeymasterDefs.KM_PAD_RSA_OAEP:
+ return RSA_OAEP;
+ case KeymasterDefs.KM_PAD_RSA_PSS:
+ return RSA_PSS;
default:
throw new IllegalArgumentException("Unknown padding: " + padding);
}
@@ -283,6 +372,14 @@
return "NONE";
case PKCS7:
return "PKCS#7";
+ case RSA_PKCS1_ENCRYPTION:
+ return "RSA PKCS#1 (encryption)";
+ case RSA_PKCS1_SIGNATURE:
+ return "RSA PKCS#1 (signature)";
+ case RSA_OAEP:
+ return "RSA OAEP";
+ case RSA_PSS:
+ return "RSA PSS";
default:
throw new IllegalArgumentException("Unknown padding: " + padding);
}
@@ -291,12 +388,18 @@
/**
* @hide
*/
- public static @PaddingEnum int fromJCAPadding(String padding) {
+ public static @PaddingEnum int fromJCACipherPadding(String padding) {
String paddingLower = padding.toLowerCase(Locale.US);
if ("nopadding".equals(paddingLower)) {
return NONE;
} else if ("pkcs7padding".equals(paddingLower)) {
return PKCS7;
+ } else if ("pkcs1padding".equals(paddingLower)) {
+ return RSA_PKCS1_ENCRYPTION;
+ } else if (("oaeppadding".equals(paddingLower))
+ || ((paddingLower.startsWith("oaepwith"))
+ && (paddingLower.endsWith("padding")))) {
+ return RSA_OAEP;
} else {
throw new IllegalArgumentException("Unknown padding: " + padding);
}
@@ -327,7 +430,15 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true,
- value = {Digest.NONE, Digest.SHA256})
+ value = {
+ Digest.NONE,
+ Digest.MD5,
+ Digest.SHA1,
+ Digest.SHA224,
+ Digest.SHA256,
+ Digest.SHA384,
+ Digest.SHA512,
+ })
public @interface DigestEnum {}
/**
@@ -343,9 +454,34 @@
public static final int NONE = 1 << 0;
/**
- * SHA-256 digest.
+ * MD5 digest.
*/
- public static final int SHA256 = 1 << 1;
+ public static final int MD5 = 1 << 1;
+
+ /**
+ * SHA-1 digest.
+ */
+ public static final int SHA1 = 1 << 2;
+
+ /**
+ * SHA-2 224 (aka SHA-224) digest.
+ */
+ public static final int SHA224 = 1 << 3;
+
+ /**
+ * SHA-2 256 (aka SHA-256) digest.
+ */
+ public static final int SHA256 = 1 << 4;
+
+ /**
+ * SHA-2 384 (aka SHA-384) digest.
+ */
+ public static final int SHA384 = 1 << 5;
+
+ /**
+ * SHA-2 512 (aka SHA-512) digest.
+ */
+ public static final int SHA512 = 1 << 6;
/**
* @hide
@@ -354,8 +490,18 @@
switch (digest) {
case NONE:
return "NONE";
+ case MD5:
+ return "MD5";
+ case SHA1:
+ return "SHA-1";
+ case SHA224:
+ return "SHA-224";
case SHA256:
- return "SHA256";
+ return "SHA-256";
+ case SHA384:
+ return "SHA-384";
+ case SHA512:
+ return "SHA-512";
default:
throw new IllegalArgumentException("Unknown digest: " + digest);
}
@@ -364,13 +510,19 @@
/**
* @hide
*/
- public static String[] allToString(@DigestEnum int digests) {
- int[] values = getSetFlags(digests);
- String[] result = new String[values.length];
- for (int i = 0; i < values.length; i++) {
- result[i] = toString(values[i]);
+ public static String allToString(@DigestEnum int digests) {
+ StringBuilder result = new StringBuilder("[");
+ boolean firstValue = true;
+ for (@DigestEnum int digest : getSetFlags(digests)) {
+ if (firstValue) {
+ firstValue = false;
+ } else {
+ result.append(", ");
+ }
+ result.append(toString(digest));
}
- return result;
+ result.append(']');
+ return result.toString();
}
/**
@@ -380,8 +532,18 @@
switch (digest) {
case NONE:
return KeymasterDefs.KM_DIGEST_NONE;
+ case MD5:
+ return KeymasterDefs.KM_DIGEST_MD5;
+ case SHA1:
+ return KeymasterDefs.KM_DIGEST_SHA1;
+ case SHA224:
+ return KeymasterDefs.KM_DIGEST_SHA_2_224;
case SHA256:
return KeymasterDefs.KM_DIGEST_SHA_2_256;
+ case SHA384:
+ return KeymasterDefs.KM_DIGEST_SHA_2_384;
+ case SHA512:
+ return KeymasterDefs.KM_DIGEST_SHA_2_512;
default:
throw new IllegalArgumentException("Unknown digest: " + digest);
}
@@ -394,8 +556,18 @@
switch (digest) {
case KeymasterDefs.KM_DIGEST_NONE:
return NONE;
+ case KeymasterDefs.KM_DIGEST_MD5:
+ return MD5;
+ case KeymasterDefs.KM_DIGEST_SHA1:
+ return SHA1;
+ case KeymasterDefs.KM_DIGEST_SHA_2_224:
+ return SHA224;
case KeymasterDefs.KM_DIGEST_SHA_2_256:
return SHA256;
+ case KeymasterDefs.KM_DIGEST_SHA_2_384:
+ return SHA384;
+ case KeymasterDefs.KM_DIGEST_SHA_2_512:
+ return SHA512;
default:
throw new IllegalArgumentException("Unknown digest: " + digest);
}
@@ -429,11 +601,21 @@
public static @DigestEnum Integer fromJCASecretKeyAlgorithm(String algorithm) {
String algorithmLower = algorithm.toLowerCase(Locale.US);
if (algorithmLower.startsWith("hmac")) {
- if ("hmacsha256".equals(algorithmLower)) {
+ String digestLower = algorithmLower.substring("hmac".length());
+ if ("md5".equals(digestLower)) {
+ return MD5;
+ } else if ("sha1".equals(digestLower)) {
+ return SHA1;
+ } else if ("sha224".equals(digestLower)) {
+ return SHA224;
+ } else if ("sha256".equals(digestLower)) {
return SHA256;
+ } else if ("sha384".equals(digestLower)) {
+ return SHA384;
+ } else if ("sha512".equals(digestLower)) {
+ return SHA512;
} else {
- throw new IllegalArgumentException("Unsupported digest: "
- + algorithmLower.substring("hmac".length()));
+ throw new IllegalArgumentException("Unsupported digest: " + digestLower);
}
} else {
return null;
@@ -447,8 +629,18 @@
switch (digest) {
case NONE:
return "NONE";
+ case MD5:
+ return "MD5";
+ case SHA1:
+ return "SHA1";
+ case SHA224:
+ return "SHA224";
case SHA256:
return "SHA256";
+ case SHA384:
+ return "SHA384";
+ case SHA512:
+ return "SHA512";
default:
throw new IllegalArgumentException("Unknown digest: " + digest);
}
@@ -461,8 +653,18 @@
switch (digest) {
case NONE:
return null;
+ case MD5:
+ return 128 / 8;
+ case SHA1:
+ return 160 / 8;
+ case SHA224:
+ return 224 / 8;
case SHA256:
return 256 / 8;
+ case SHA384:
+ return 384 / 8;
+ case SHA512:
+ return 512 / 8;
default:
throw new IllegalArgumentException("Unknown digest: " + digest);
}
@@ -471,7 +673,7 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true,
- value = {BlockMode.ECB, BlockMode.CBC, BlockMode.CTR})
+ value = {BlockMode.ECB, BlockMode.CBC, BlockMode.CTR, BlockMode.GCM})
public @interface BlockModeEnum {}
/**
@@ -489,6 +691,17 @@
/** Counter (CTR) block mode. */
public static final int CTR = 1 << 2;
+ /** Galois/Counter Mode (GCM) block mode. */
+ public static final int GCM = 1 << 3;
+
+ /**
+ * Set of block modes compatible with IND-CPA if used correctly.
+ *
+ * @hide
+ */
+ public static final @BlockModeEnum int IND_CPA_COMPATIBLE_MODES =
+ CBC | CTR | GCM;
+
/**
* @hide
*/
@@ -500,6 +713,8 @@
return KeymasterDefs.KM_MODE_CBC;
case CTR:
return KeymasterDefs.KM_MODE_CTR;
+ case GCM:
+ return KeymasterDefs.KM_MODE_GCM;
default:
throw new IllegalArgumentException("Unknown block mode: " + mode);
}
@@ -516,6 +731,8 @@
return CBC;
case KeymasterDefs.KM_MODE_CTR:
return CTR;
+ case KeymasterDefs.KM_MODE_GCM:
+ return GCM;
default:
throw new IllegalArgumentException("Unknown block mode: " + mode);
}
@@ -554,6 +771,8 @@
return "CBC";
case CTR:
return "CTR";
+ case GCM:
+ return "GCM";
default:
throw new IllegalArgumentException("Unknown block mode: " + mode);
}
@@ -562,6 +781,24 @@
/**
* @hide
*/
+ public static String allToString(@BlockModeEnum int modes) {
+ StringBuilder result = new StringBuilder("[");
+ boolean firstValue = true;
+ for (@BlockModeEnum int mode : getSetFlags(modes)) {
+ if (firstValue) {
+ firstValue = false;
+ } else {
+ result.append(", ");
+ }
+ result.append(toString(mode));
+ }
+ result.append(']');
+ return result.toString();
+ }
+
+ /**
+ * @hide
+ */
public static @BlockModeEnum int fromJCAMode(String mode) {
String modeLower = mode.toLowerCase(Locale.US);
if ("ecb".equals(modeLower)) {
@@ -570,6 +807,8 @@
return CBC;
} else if ("ctr".equals(modeLower)) {
return CTR;
+ } else if ("gcm".equals(modeLower)) {
+ return GCM;
} else {
throw new IllegalArgumentException("Unknown block mode: " + mode);
}
diff --git a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
index abce32d..b39d16d 100644
--- a/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/KeyStoreKeyGeneratorSpi.java
@@ -41,12 +41,41 @@
}
}
- public static class HmacSHA256 extends KeyStoreKeyGeneratorSpi {
- public HmacSHA256() {
+ protected static abstract class HmacBase extends KeyStoreKeyGeneratorSpi {
+ protected HmacBase(@KeyStoreKeyConstraints.DigestEnum int digest) {
super(KeyStoreKeyConstraints.Algorithm.HMAC,
- KeyStoreKeyConstraints.Digest.SHA256,
- KeyStoreKeyConstraints.Digest.getOutputSizeBytes(
- KeyStoreKeyConstraints.Digest.SHA256) * 8);
+ digest,
+ KeyStoreKeyConstraints.Digest.getOutputSizeBytes(digest) * 8);
+ }
+ }
+
+ public static class HmacSHA1 extends HmacBase {
+ public HmacSHA1() {
+ super(KeyStoreKeyConstraints.Digest.SHA1);
+ }
+ }
+
+ public static class HmacSHA224 extends HmacBase {
+ public HmacSHA224() {
+ super(KeyStoreKeyConstraints.Digest.SHA224);
+ }
+ }
+
+ public static class HmacSHA256 extends HmacBase {
+ public HmacSHA256() {
+ super(KeyStoreKeyConstraints.Digest.SHA256);
+ }
+ }
+
+ public static class HmacSHA384 extends HmacBase {
+ public HmacSHA384() {
+ super(KeyStoreKeyConstraints.Digest.SHA384);
+ }
+ }
+
+ public static class HmacSHA512 extends HmacBase {
+ public HmacSHA512() {
+ super(KeyStoreKeyConstraints.Digest.SHA512);
}
}
@@ -109,13 +138,26 @@
}
int keySizeBits = (spec.getKeySize() != null) ? spec.getKeySize() : mDefaultKeySizeBits;
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, keySizeBits);
- int purposes = spec.getPurposes();
+ @KeyStoreKeyConstraints.PurposeEnum int purposes = spec.getPurposes();
+ @KeyStoreKeyConstraints.BlockModeEnum int blockModes = spec.getBlockModes();
+ if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
+ && (spec.isRandomizedEncryptionRequired())) {
+ @KeyStoreKeyConstraints.BlockModeEnum int incompatibleBlockModes =
+ blockModes & ~KeyStoreKeyConstraints.BlockMode.IND_CPA_COMPATIBLE_MODES;
+ if (incompatibleBlockModes != 0) {
+ throw new IllegalStateException(
+ "Randomized encryption (IND-CPA) required but may be violated by block"
+ + " mode(s): "
+ + KeyStoreKeyConstraints.BlockMode.allToString(incompatibleBlockModes)
+ + ". See KeyGeneratorSpec documentation.");
+ }
+ }
+
for (int keymasterPurpose :
KeyStoreKeyConstraints.Purpose.allToKeymaster(purposes)) {
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, keymasterPurpose);
}
- for (int keymasterBlockMode :
- KeyStoreKeyConstraints.BlockMode.allToKeymaster(spec.getBlockModes())) {
+ for (int keymasterBlockMode : KeyStoreKeyConstraints.BlockMode.allToKeymaster(blockModes)) {
args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, keymasterBlockMode);
}
for (int keymasterPadding :
@@ -148,8 +190,8 @@
? spec.getKeyValidityForConsumptionEnd() : new Date(Long.MAX_VALUE));
if (((purposes & KeyStoreKeyConstraints.Purpose.ENCRYPT) != 0)
- || ((purposes & KeyStoreKeyConstraints.Purpose.DECRYPT) != 0)) {
- // Permit caller-specified IV. This is needed due to the Cipher abstraction.
+ && (!spec.isRandomizedEncryptionRequired())) {
+ // Permit caller-provided IV when encrypting with this key
args.addBoolean(KeymasterDefs.KM_TAG_CALLER_NONCE);
}
diff --git a/keystore/java/android/security/KeyStoreKeySpec.java b/keystore/java/android/security/KeyStoreKeySpec.java
index 256d9b3..65bb236 100644
--- a/keystore/java/android/security/KeyStoreKeySpec.java
+++ b/keystore/java/android/security/KeyStoreKeySpec.java
@@ -28,6 +28,7 @@
public class KeyStoreKeySpec implements KeySpec {
private final String mKeystoreAlias;
private final int mKeySize;
+ private final boolean mTeeBacked;
private final @KeyStoreKeyCharacteristics.OriginEnum int mOrigin;
private final Date mKeyValidityStart;
private final Date mKeyValidityForOriginationEnd;
@@ -46,6 +47,7 @@
* @hide
*/
KeyStoreKeySpec(String keystoreKeyAlias,
+ boolean teeBacked,
@KeyStoreKeyCharacteristics.OriginEnum int origin,
int keySize,
Date keyValidityStart,
@@ -61,6 +63,7 @@
int userAuthenticationValidityDurationSeconds,
boolean invalidatedOnNewFingerprintEnrolled) {
mKeystoreAlias = keystoreKeyAlias;
+ mTeeBacked = teeBacked;
mOrigin = origin;
mKeySize = keySize;
mKeyValidityStart = keyValidityStart;
@@ -85,6 +88,14 @@
}
/**
+ * Returns {@code true} if the key is TEE-backed. Key material of TEE-backed keys is available
+ * in plaintext only inside the TEE.
+ */
+ public boolean isTeeBacked() {
+ return mTeeBacked;
+ }
+
+ /**
* Gets the origin of the key.
*/
public @KeyStoreKeyCharacteristics.OriginEnum int getOrigin() {
diff --git a/keystore/java/android/security/KeyStoreParameter.java b/keystore/java/android/security/KeyStoreParameter.java
index 0b2f9b6..751eef5 100644
--- a/keystore/java/android/security/KeyStoreParameter.java
+++ b/keystore/java/android/security/KeyStoreParameter.java
@@ -19,13 +19,14 @@
import android.content.Context;
import java.security.Key;
-import java.security.KeyPairGenerator;
import java.security.KeyStore.ProtectionParameter;
import java.util.Date;
+import javax.crypto.Cipher;
+
/**
- * This provides the optional parameters that can be specified for
- * {@code KeyStore} entries that work with
+ * Parameters specifying how to secure and restrict the use of a key being
+ * imported into the
* <a href="{@docRoot}training/articles/keystore.html">Android KeyStore
* facility</a>. The Android KeyStore facility is accessed through a
* {@link java.security.KeyStore} API using the {@code AndroidKeyStore}
@@ -36,12 +37,6 @@
* there is only one logical instance of the {@code KeyStore} per application
* UID so apps using the {@code sharedUid} facility will also share a
* {@code KeyStore}.
- * <p>
- * Keys may be generated using the {@link KeyPairGenerator} facility with a
- * {@link KeyPairGeneratorSpec} to specify the entry's {@code alias}. A
- * self-signed X.509 certificate will be attached to generated entries, but that
- * may be replaced at a later time by a certificate signed by a real Certificate
- * Authority.
*/
public final class KeyStoreParameter implements ProtectionParameter {
private int mFlags;
@@ -52,6 +47,7 @@
private final @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
private final @KeyStoreKeyConstraints.DigestEnum Integer mDigests;
private final @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private final boolean mRandomizedEncryptionRequired;
private final @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private final int mUserAuthenticationValidityDurationSeconds;
private final boolean mInvalidatedOnNewFingerprintEnrolled;
@@ -64,6 +60,7 @@
@KeyStoreKeyConstraints.PaddingEnum int paddings,
@KeyStoreKeyConstraints.DigestEnum Integer digests,
@KeyStoreKeyConstraints.BlockModeEnum int blockModes,
+ boolean randomizedEncryptionRequired,
@KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators,
int userAuthenticationValidityDurationSeconds,
boolean invalidatedOnNewFingerprintEnrolled) {
@@ -81,6 +78,7 @@
mPaddings = paddings;
mDigests = digests;
mBlockModes = blockModes;
+ mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticators = userAuthenticators;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mInvalidatedOnNewFingerprintEnrolled = invalidatedOnNewFingerprintEnrolled;
@@ -188,6 +186,21 @@
}
/**
+ * Returns {@code true} if encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic property
+ * being required is <em>indistinguishability under chosen-plaintext attack ({@code
+ * IND-CPA})</em>. This property is important because it mitigates several classes of
+ * weaknesses due to which ciphertext may leak information about plaintext. For example, if a
+ * given plaintext always produces the same ciphertext, an attacker may see the repeated
+ * ciphertexts and be able to deduce something about the plaintext.
+ *
+ * @hide
+ */
+ public boolean isRandomizedEncryptionRequired() {
+ return mRandomizedEncryptionRequired;
+ }
+
+ /**
* Gets the set of user authenticators which protect access to this key. The key can only be
* used iff the user has authenticated to at least one of these user authenticators.
*
@@ -251,6 +264,7 @@
private @KeyStoreKeyConstraints.PaddingEnum int mPaddings;
private @KeyStoreKeyConstraints.DigestEnum Integer mDigests;
private @KeyStoreKeyConstraints.BlockModeEnum int mBlockModes;
+ private boolean mRandomizedEncryptionRequired = true;
private @KeyStoreKeyConstraints.UserAuthenticatorEnum int mUserAuthenticators;
private int mUserAuthenticationValidityDurationSeconds = -1;
private boolean mInvalidatedOnNewFingerprintEnrolled;
@@ -287,7 +301,7 @@
/**
* Sets the time instant before which the key is not yet valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityEnd(Date)
*
@@ -301,7 +315,7 @@
/**
* Sets the time instant after which the key is no longer valid.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityStart(Date)
* @see #setKeyValidityForConsumptionEnd(Date)
@@ -318,7 +332,7 @@
/**
* Sets the time instant after which the key is no longer valid for encryption and signing.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForConsumptionEnd(Date)
*
@@ -333,7 +347,7 @@
* Sets the time instant after which the key is no longer valid for decryption and
* verification.
*
- * <b>By default, the key is valid at any instant.
+ * <p>By default, the key is valid at any instant.
*
* @see #setKeyValidityForOriginationEnd(Date)
*
@@ -398,6 +412,47 @@
}
/**
+ * Sets whether encryption using this key must be sufficiently randomized to produce
+ * different ciphertexts for the same plaintext every time. The formal cryptographic
+ * property being required is <em>indistinguishability under chosen-plaintext attack
+ * ({@code IND-CPA})</em>. This property is important because it mitigates several classes
+ * of weaknesses due to which ciphertext may leak information about plaintext. For example,
+ * if a given plaintext always produces the same ciphertext, an attacker may see the
+ * repeated ciphertexts and be able to deduce something about the plaintext.
+ *
+ * <p>By default, {@code IND-CPA} is required.
+ *
+ * <p>When {@code IND-CPA} is required:
+ * <ul>
+ * <li>transformation which do not offer {@code IND-CPA}, such as symmetric ciphers using
+ * {@code ECB} mode or RSA encryption without padding, are prohibited;</li>
+ * <li>in transformations which use an IV, such as symmetric ciphers in {@code CBC},
+ * {@code CTR}, and {@code GCM} block modes, caller-provided IVs are rejected when
+ * encrypting, to ensure that only random IVs are used.</li>
+ *
+ * <p>Before disabling this requirement, consider the following approaches instead:
+ * <ul>
+ * <li>If you are generating a random IV for encryption and then initializing a {@code}
+ * Cipher using the IV, the solution is to let the {@code Cipher} generate a random IV
+ * instead. This will occur if the {@code Cipher} is initialized for encryption without an
+ * IV. The IV can then be queried via {@link Cipher#getIV()}.</li>
+ * <li>If you are generating a non-random IV (e.g., an IV derived from something not fully
+ * random, such as the name of the file being encrypted, or transaction ID, or password,
+ * or a device identifier), consider changing your design to use a random IV which will then
+ * be provided in addition to the ciphertext to the entities which need to decrypt the
+ * ciphertext.</li>
+ * <li>If you are using RSA encryption without padding, consider switching to padding
+ * schemes which offer {@code IND-CPA}, such as PKCS#1 or OAEP.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ public Builder setRandomizedEncryptionRequired(boolean required) {
+ mRandomizedEncryptionRequired = required;
+ return this;
+ }
+
+ /**
* Sets the user authenticators which protect access to this key. The key can only be used
* iff the user has authenticated to at least one of these user authenticators.
*
@@ -465,6 +520,7 @@
mPaddings,
mDigests,
mBlockModes,
+ mRandomizedEncryptionRequired,
mUserAuthenticators,
mUserAuthenticationValidityDurationSeconds,
mInvalidatedOnNewFingerprintEnrolled);
diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
index 8bf228a..a5e87d1 100644
--- a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
@@ -70,7 +70,8 @@
+ " Keystore error: " + errorCode);
}
- @KeyStoreKeyCharacteristics.OriginEnum Integer origin;
+ boolean teeBacked;
+ @KeyStoreKeyCharacteristics.OriginEnum int origin;
int keySize;
@KeyStoreKeyConstraints.PurposeEnum int purposes;
@KeyStoreKeyConstraints.AlgorithmEnum int algorithm;
@@ -80,11 +81,17 @@
@KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators;
@KeyStoreKeyConstraints.UserAuthenticatorEnum int teeEnforcedUserAuthenticators;
try {
- origin = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_ORIGIN);
- if (origin == null) {
+ if (keyCharacteristics.hwEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) {
+ teeBacked = true;
+ origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(
+ keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1));
+ } else if (keyCharacteristics.swEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) {
+ teeBacked = false;
+ origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(
+ keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1));
+ } else {
throw new InvalidKeySpecException("Key origin not available");
}
- origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(origin);
Integer keySizeInteger =
KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_KEY_SIZE);
if (keySizeInteger == null) {
@@ -147,6 +154,7 @@
boolean invalidatedOnNewFingerprintEnrolled = false;
return new KeyStoreKeySpec(entryAlias,
+ teeBacked,
origin,
keySize,
keyValidityStart,
diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java
index c9a140c..6e3f8be 100644
--- a/keystore/tests/src/android/security/KeyStoreTest.java
+++ b/keystore/tests/src/android/security/KeyStoreTest.java
@@ -798,7 +798,7 @@
// TODO: Verify we have an RSA public key that's well formed.
}
- public void testAesOcbEncryptSuccess() throws Exception {
+ public void testAesGcmEncryptSuccess() throws Exception {
String name = "test";
KeymasterArguments args = new KeymasterArguments();
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
@@ -806,7 +806,7 @@
args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB);
+ args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM);
args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096);
args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16);
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
@@ -903,9 +903,7 @@
args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB);
- args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096);
- args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16);
+ args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR);
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
KeyCharacteristics outCharacteristics = new KeyCharacteristics();
@@ -935,11 +933,9 @@
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
- args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+ args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_PKCS7);
args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
- args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB);
- args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096);
- args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16);
+ args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 1);
KeyCharacteristics outCharacteristics = new KeyCharacteristics();
diff --git a/libs/hwui/AmbientShadow.cpp b/libs/hwui/AmbientShadow.cpp
index 0a210d6..a4100a2 100644
--- a/libs/hwui/AmbientShadow.cpp
+++ b/libs/hwui/AmbientShadow.cpp
@@ -45,8 +45,9 @@
/**
* Other constants:
*/
-// For the edge of the penumbra, the opacity is 0.
-#define OUTER_OPACITY (0.0f)
+// For the edge of the penumbra, the opacity is 0. After transform (1 - alpha),
+// it is 1.
+#define TRANSFORMED_OUTER_OPACITY (1.0f)
// Once the alpha difference is greater than this threshold, we will allocate extra
// edge vertices.
@@ -83,11 +84,13 @@
return 1.0 / (1 + MathUtils::max(factoredZ, 0.0f));
}
+// The shader is using gaussian function e^-(1-x)*(1-x)*4, therefore, we transform
+// the alpha value to (1 - alpha)
inline float getTransformedAlphaFromAlpha(float alpha) {
- return acosf(1.0f - 2.0f * alpha);
+ return 1.0f - alpha;
}
-// The output is ranged from 0 to M_PI.
+// The output is ranged from 0 to 1.
inline float getTransformedAlphaFromFactoredZ(float factoredZ) {
return getTransformedAlphaFromAlpha(getAlphaFromFactoredZ(factoredZ));
}
@@ -249,7 +252,7 @@
indexBuffer[indexBufferIndex++] = vertexBufferIndex;
indexBuffer[indexBufferIndex++] = currentInnerVertexIndex;
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], outerVertex.x,
- outerVertex.y, OUTER_OPACITY);
+ outerVertex.y, TRANSFORMED_OUTER_OPACITY);
if (j == 0) {
outerStart = outerVertex;
@@ -285,7 +288,7 @@
(outerLast * startWeight + outerNext * k) / extraVerticesNumber;
indexBuffer[indexBufferIndex++] = vertexBufferIndex;
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], currentOuter.x,
- currentOuter.y, OUTER_OPACITY);
+ currentOuter.y, TRANSFORMED_OUTER_OPACITY);
if (!isCasterOpaque) {
umbraVertices[umbraIndex++] = vertexBufferIndex;
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 56380db..c79ae77 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -164,6 +164,8 @@
for (uint32_t i = 0; i < mRGBACacheTextures.size(); i++) {
mRGBACacheTextures[i]->init();
}
+
+ mDrawn = false;
}
void FontRenderer::flushLargeCaches(Vector<CacheTexture*>& cacheTextures) {
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index e9b22e2..41adda1 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -240,8 +240,9 @@
const char* gFS_Main_ApplyVertexAlphaLinearInterp =
" fragColor *= alpha;\n";
const char* gFS_Main_ApplyVertexAlphaShadowInterp =
- " fragColor *= (1.0 - cos(alpha)) / 2.0;\n";
-
+ // Use a gaussian function for the shadow fall off. Note that alpha here
+ // is actually (1.0 - alpha) for saving computation.
+ " fragColor *= exp(- alpha * alpha * 4.0) - 0.018;\n";
const char* gFS_Main_FetchTexture[2] = {
// Don't modulate
" fragColor = texture2D(baseSampler, outTexCoords);\n",
diff --git a/libs/hwui/RenderBufferCache.cpp b/libs/hwui/RenderBufferCache.cpp
index 0380c51..d0812c9 100644
--- a/libs/hwui/RenderBufferCache.cpp
+++ b/libs/hwui/RenderBufferCache.cpp
@@ -158,6 +158,11 @@
buffer->getWidth(), buffer->getHeight());
return true;
+ } else {
+ RENDER_BUFFER_LOGD("Deleted %s render buffer (%dx%d) Size=%d, MaxSize=%d",
+ RenderBuffer::formatName(buffer->getFormat()),
+ buffer->getWidth(), buffer->getHeight(), size, mMaxSize);
+ delete buffer;
}
return false;
}
diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp
index b3b06d6..db3c2d9 100644
--- a/libs/hwui/SpotShadow.cpp
+++ b/libs/hwui/SpotShadow.cpp
@@ -44,6 +44,9 @@
// For each RADIANS_DIVISOR, we would allocate one more vertex b/t the normals.
#define SPOT_CORNER_RADIANS_DIVISOR (M_PI / SPOT_EXTRA_CORNER_VERTEX_PER_PI)
+// For performance, we use (1 - alpha) value for the shader input.
+#define TRANSFORMED_PENUMBRA_ALPHA 1.0f
+#define TRANSFORMED_UMBRA_ALPHA 0.0f
#include <math.h>
#include <stdlib.h>
@@ -964,11 +967,11 @@
// Fill the IB and VB for the penumbra area.
for (int i = 0; i < newPenumbraLength; i++) {
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], newPenumbra[i].x,
- newPenumbra[i].y, 0.0f);
+ newPenumbra[i].y, TRANSFORMED_PENUMBRA_ALPHA);
}
for (int i = 0; i < umbraLength; i++) {
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], umbra[i].x, umbra[i].y,
- M_PI);
+ TRANSFORMED_UMBRA_ALPHA);
}
for (int i = 0; i < verticesPairIndex; i++) {
@@ -1008,14 +1011,14 @@
indexBuffer[indexBufferIndex++] = newPenumbraLength + i;
indexBuffer[indexBufferIndex++] = vertexBufferIndex;
AlphaVertex::set(&shadowVertices[vertexBufferIndex++],
- closerVertex.x, closerVertex.y, M_PI);
+ closerVertex.x, closerVertex.y, TRANSFORMED_UMBRA_ALPHA);
}
} else {
// If there is no occluded umbra at all, then draw the triangle fan
// starting from the centroid to all umbra vertices.
int lastCentroidIndex = vertexBufferIndex;
AlphaVertex::set(&shadowVertices[vertexBufferIndex++], centroid.x,
- centroid.y, M_PI);
+ centroid.y, TRANSFORMED_UMBRA_ALPHA);
for (int i = 0; i < umbraLength; i++) {
indexBuffer[indexBufferIndex++] = newPenumbraLength + i;
indexBuffer[indexBufferIndex++] = lastCentroidIndex;
diff --git a/location/java/android/location/FusedBatchOptions.java b/location/java/android/location/FusedBatchOptions.java
index 5600aeb..aa4a860 100644
--- a/location/java/android/location/FusedBatchOptions.java
+++ b/location/java/android/location/FusedBatchOptions.java
@@ -30,6 +30,8 @@
// the default value is set to request fixes at no cost
private volatile double mMaxPowerAllocationInMW = 0;
+ // If non-zero can be used for power savings by throttling location when device hasn't moved.
+ private volatile float mSmallestDisplacementMeters = 0;
/*
* Getters and setters for properties needed to hold the options.
@@ -50,6 +52,14 @@
return mPeriodInNS;
}
+ public void setSmallestDisplacementMeters(float value) {
+ mSmallestDisplacementMeters = value;
+ }
+
+ public float getSmallestDisplacementMeters() {
+ return mSmallestDisplacementMeters;
+ }
+
public void setSourceToUse(int source) {
mSourcesToUse |= source;
}
@@ -112,6 +122,7 @@
options.setPeriodInNS(parcel.readLong());
options.setSourceToUse(parcel.readInt());
options.setFlag(parcel.readInt());
+ options.setSmallestDisplacementMeters(parcel.readFloat());
return options;
}
@@ -132,5 +143,6 @@
parcel.writeLong(mPeriodInNS);
parcel.writeInt(mSourcesToUse);
parcel.writeInt(mFlags);
+ parcel.writeFloat(mSmallestDisplacementMeters);
}
}
diff --git a/location/lib/java/com/android/location/provider/GmsFusedBatchOptions.java b/location/lib/java/com/android/location/provider/GmsFusedBatchOptions.java
index fd3f402..29818ec 100644
--- a/location/lib/java/com/android/location/provider/GmsFusedBatchOptions.java
+++ b/location/lib/java/com/android/location/provider/GmsFusedBatchOptions.java
@@ -43,6 +43,14 @@
return mOptions.getPeriodInNS();
}
+ public void setSmallestDisplacementMeters(float value) {
+ mOptions.setSmallestDisplacementMeters(value);
+ }
+
+ public float getSmallestDisplacementMeters() {
+ return mOptions.getSmallestDisplacementMeters();
+ }
+
public void setSourceToUse(int source) {
mOptions.setSourceToUse(source);
}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index fd7fca6..e028e3f 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -325,6 +325,13 @@
*/
public static final int BUFFER_FLAG_END_OF_STREAM = 4;
+ /**
+ * This indicates that the codec is released because the media resources used by the codec
+ * have been reclaimed, for example by the resource manager.
+ * This is used by the {@link Callback#onCodecReleased} callback.
+ */
+ public static final int REASON_RECLAIMED = 1;
+
private EventHandler mEventHandler;
private Callback mCallback;
@@ -335,6 +342,7 @@
private static final int CB_OUTPUT_AVAILABLE = 2;
private static final int CB_ERROR = 3;
private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
+ private static final int CB_CODEC_RELEASED = 5;
private class EventHandler extends Handler {
private MediaCodec mCodec;
@@ -405,6 +413,13 @@
break;
}
+ case CB_CODEC_RELEASED:
+ {
+ int reason = msg.arg2;
+ mCallback.onCodecReleased(mCodec, reason);
+ break;
+ }
+
default:
{
break;
@@ -720,6 +735,7 @@
}
/* Must be in sync with android_media_MediaCodec.cpp */
+ private final static int ACTION_FATAL = 0;
private final static int ACTION_TRANSIENT = 1;
private final static int ACTION_RECOVERABLE = 2;
@@ -1654,6 +1670,22 @@
* @param format The new output format.
*/
public abstract void onOutputFormatChanged(MediaCodec codec, MediaFormat format);
+
+ /**
+ * Called when the underlying codec component has been released.
+ * <p>
+ * At this point the MediaCodec must be released, as it has moved to terminal
+ * Uninitialized state.
+ *
+ * @param codec The MediaCodec object.
+ * @param reason The reason of the release.
+ */
+ public void onCodecReleased(MediaCodec codec, int reason) {
+ int errorCode = -1;
+ String detailMessage = "resources reclaimed";
+ onError(codec,
+ new CodecException(errorCode, CodecException.ACTION_FATAL, detailMessage));
+ }
}
private void postEventFromNative(
diff --git a/media/java/android/media/MediaCrypto.java b/media/java/android/media/MediaCrypto.java
index c7c3fc2..da81b37 100644
--- a/media/java/android/media/MediaCrypto.java
+++ b/media/java/android/media/MediaCrypto.java
@@ -70,6 +70,20 @@
*/
public final native boolean requiresSecureDecoderComponent(String mime);
+ /**
+ * Associate a MediaDrm session with this MediaCrypto instance. The
+ * MediaDrm session is used to securely load decryption keys for a
+ * crypto scheme. The crypto keys loaded through the MediaDrm session
+ * may be selected for use during the decryption operation performed
+ * by {@link android.media.MediaCodec#queueSecureInputBuffer} by specifying
+ * their key ids in the {@link android.media.MediaCodec.CryptoInfo#key} field.
+ * @param sessionId the MediaDrm sessionId to associate with this
+ * MediaCrypto instance
+ * @throws MediaCryptoException on failure to set the sessionId
+ */
+ public final native void setMediaDrmSession(byte[] sessionId)
+ throws MediaCryptoException;
+
@Override
protected void finalize() {
native_finalize();
diff --git a/media/java/android/media/MediaCryptoException.java b/media/java/android/media/MediaCryptoException.java
index 44c5222..703e96f 100644
--- a/media/java/android/media/MediaCryptoException.java
+++ b/media/java/android/media/MediaCryptoException.java
@@ -17,8 +17,8 @@
package android.media;
/**
- * Exception thrown if MediaCrypto object could not be instantiated for
- * whatever reason.
+ * Exception thrown if MediaCrypto object could not be instantiated or
+ * if unable to perform an operation on the MediaCrypto object.
*/
public final class MediaCryptoException extends Exception {
public MediaCryptoException(String detailMessage) {
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 069f7ff..fc5fc43 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -17,9 +17,10 @@
package android.media;
import java.lang.ref.WeakReference;
-import java.util.UUID;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.UUID;
import android.annotation.SystemApi;
import android.os.Handler;
import android.os.Looper;
@@ -98,12 +99,14 @@
*/
public final class MediaDrm {
- private final static String TAG = "MediaDrm";
+ private static final String TAG = "MediaDrm";
private static final String PERMISSION = android.Manifest.permission.ACCESS_DRM_CERTIFICATES;
private EventHandler mEventHandler;
private OnEventListener mOnEventListener;
+ private OnKeysChangeListener mOnKeysChangeListener;
+ private OnExpirationUpdateListener mOnExpirationUpdateListener;
private long mNativeContext;
@@ -227,6 +230,148 @@
}
/**
+ * Register a callback to be invoked when a session expiration update
+ * occurs. The app's OnExpirationUpdateListener will be notified
+ * when the expiration time of the keys in the session have changed.
+ * @param listener the callback that will be run
+ * @param handler the handler on which the listener should be invoked, or
+ * null if the listener should be invoked on the calling thread's looper.
+ */
+ public void setOnExpirationUpdateListener(OnExpirationUpdateListener listener,
+ Handler handler)
+ {
+ if (listener != null) {
+ Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
+ if (looper != null) {
+ if (mEventHandler == null || mEventHandler.getLooper() != looper) {
+ mEventHandler = new EventHandler(this, looper);
+ }
+ }
+ }
+ mOnExpirationUpdateListener = listener;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a drm session
+ * expiration update occurs
+ */
+ public interface OnExpirationUpdateListener
+ {
+ /**
+ * Called when a session expiration update occurs, to inform the app
+ * about the change in expiration time
+ *
+ * @param md the MediaDrm object on which the event occurred
+ * @param sessionId the DRM session ID on which the event occurred
+ * @param expirationTime the new expiration time for the keys in the session.
+ * The time is in milliseconds, relative to the Unix epoch.
+ */
+ void onExpirationUpdate(MediaDrm md, byte[] sessionId, long expirationTime);
+ }
+
+ /**
+ * Register a callback to be invoked when the state of keys in a session
+ * change, e.g. when a license update occurs or when a license expires.
+ *
+ * @param listener the callback that will be run when key status changes
+ * @param handler the handler on which the listener should be invoked, or
+ * null if the listener should be invoked on the calling thread's looper.
+ */
+ public void setOnKeysChangeListener(OnKeysChangeListener listener,
+ Handler handler)
+ {
+ if (listener != null) {
+ Looper looper = handler != null ? handler.getLooper() : Looper.myLooper();
+ if (looper != null) {
+ if (mEventHandler == null || mEventHandler.getLooper() != looper) {
+ mEventHandler = new EventHandler(this, looper);
+ }
+ }
+ }
+ mOnKeysChangeListener = listener;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the keys in a drm
+ * session change states.
+ */
+ public interface OnKeysChangeListener
+ {
+ /**
+ * Called when the keys in a session change status, such as when the license
+ * is renewed or expires.
+ *
+ * @param md the MediaDrm object on which the event occurred
+ * @param sessionId the DRM session ID on which the event occurred
+ * @param keyInformation a list of {@link MediaDrm.KeyStatus}
+ * instances indicating the status for each key in the session
+ * @param hasNewUsableKey indicates if a key has been added that is usable,
+ * which may trigger an attempt to resume playback on the media stream
+ * if it is currently blocked waiting for a key.
+ */
+ void onKeysChange(MediaDrm md, byte[] sessionId, List<KeyStatus> keyInformation,
+ boolean hasNewUsableKey);
+ }
+
+ /**
+ * The key is currently usable to decrypt media data
+ */
+ public static final int KEY_STATUS_USABLE = 0;
+
+ /**
+ * The key is no longer usable to decrypt media data because its
+ * expiration time has passed.
+ */
+ public static final int KEY_STATUS_EXPIRED = 1;
+
+ /**
+ * The key is not currently usable to decrypt media data because its
+ * output requirements cannot currently be met.
+ */
+ public static final int KEY_STATUS_OUTPUT_NOT_ALLOWED = 2;
+
+ /**
+ * The status of the key is not yet known and is being determined.
+ * The status will be updated with the actual status when it has
+ * been determined.
+ */
+ public static final int KEY_STATUS_PENDING = 3;
+
+ /**
+ * The key is not currently usable to decrypt media data because of an
+ * internal error in processing unrelated to input parameters. This error
+ * is not actionable by an app.
+ */
+ public static final int KEY_STATUS_INTERNAL_ERROR = 4;
+
+
+ /**
+ * Defines the status of a key.
+ * A KeyStatus for each key in a session is provided to the
+ * {@link OnKeysChangeListener#onKeysChange}
+ * listener.
+ */
+ public static final class KeyStatus {
+ private final byte[] mKeyId;
+ private final int mStatusCode;
+
+ KeyStatus(byte[] keyId, int statusCode) {
+ mKeyId = keyId;
+ mStatusCode = statusCode;
+ }
+
+ /**
+ * Returns the status code for the key
+ */
+ public int getStatusCode() { return mStatusCode; }
+
+ /**
+ * Returns the id for the key
+ */
+ public byte[] getKeyId() { return mKeyId; }
+ }
+
+ /**
* Register a callback to be invoked when an event occurs
*
* @param listener the callback that will be run
@@ -289,6 +434,8 @@
public static final int EVENT_SESSION_RECLAIMED = 5;
private static final int DRM_EVENT = 200;
+ private static final int EXPIRATION_UPDATE = 201;
+ private static final int KEYS_CHANGE = 202;
private class EventHandler extends Handler
{
@@ -308,8 +455,6 @@
switch(msg.what) {
case DRM_EVENT:
- Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")");
-
if (mOnEventListener != null) {
if (msg.obj != null && msg.obj instanceof Parcel) {
Parcel parcel = (Parcel)msg.obj;
@@ -321,11 +466,46 @@
if (data.length == 0) {
data = null;
}
+
+ Log.i(TAG, "Drm event (" + msg.arg1 + "," + msg.arg2 + ")");
mOnEventListener.onEvent(mMediaDrm, sessionId, msg.arg1, msg.arg2, data);
}
}
return;
+ case KEYS_CHANGE:
+ if (mOnKeysChangeListener != null) {
+ if (msg.obj != null && msg.obj instanceof Parcel) {
+ Parcel parcel = (Parcel)msg.obj;
+ byte[] sessionId = parcel.createByteArray();
+ if (sessionId.length > 0) {
+ List<KeyStatus> keyStatusList = keyStatusListFromParcel(parcel);
+ boolean hasNewUsableKey = (parcel.readInt() != 0);
+
+ Log.i(TAG, "Drm keys change");
+ mOnKeysChangeListener.onKeysChange(mMediaDrm, sessionId, keyStatusList,
+ hasNewUsableKey);
+ }
+ }
+ }
+ return;
+
+ case EXPIRATION_UPDATE:
+ if (mOnExpirationUpdateListener != null) {
+ if (msg.obj != null && msg.obj instanceof Parcel) {
+ Parcel parcel = (Parcel)msg.obj;
+ byte[] sessionId = parcel.createByteArray();
+ if (sessionId.length > 0) {
+ long expirationTime = parcel.readLong();
+
+ Log.i(TAG, "Drm key expiration update: " + expirationTime);
+ mOnExpirationUpdateListener.onExpirationUpdate(mMediaDrm, sessionId,
+ expirationTime);
+ }
+ }
+ }
+ return;
+
default:
Log.e(TAG, "Unknown message type " + msg.what);
return;
@@ -333,7 +513,21 @@
}
}
- /*
+ /**
+ * Parse a list of KeyStatus objects from an event parcel
+ */
+ private List<KeyStatus> keyStatusListFromParcel(Parcel parcel) {
+ int nelems = parcel.readInt();
+ List<KeyStatus> keyStatusList = new ArrayList(nelems);
+ while (nelems-- > 0) {
+ byte[] keyId = parcel.createByteArray();
+ int keyStatusCode = parcel.readInt();
+ keyStatusList.add(new KeyStatus(keyId, keyStatusCode));
+ }
+ return keyStatusList;
+ }
+
+ /**
* This method is called from native code when an event occurs. This method
* just uses the EventHandler system to post the event back to the main app thread.
* We use a weak reference to the original MediaPlayer object so that the native
@@ -341,14 +535,14 @@
* the cookie passed to native_setup().)
*/
private static void postEventFromNative(Object mediadrm_ref,
- int eventType, int extra, Object obj)
+ int what, int eventType, int extra, Object obj)
{
- MediaDrm md = (MediaDrm)((WeakReference)mediadrm_ref).get();
+ MediaDrm md = (MediaDrm)((WeakReference<MediaDrm>)mediadrm_ref).get();
if (md == null) {
return;
}
if (md.mEventHandler != null) {
- Message m = md.mEventHandler.obtainMessage(DRM_EVENT, eventType, extra, obj);
+ Message m = md.mEventHandler.obtainMessage(what, eventType, extra, obj);
md.mEventHandler.sendMessage(m);
}
}
@@ -404,7 +598,7 @@
/**
* Contains the opaque data an app uses to request keys from a license server
*/
- public final static class KeyRequest {
+ public static final class KeyRequest {
private byte[] mData;
private String mDefaultUrl;
private int mRequestType;
@@ -521,7 +715,7 @@
* Contains the opaque data an app uses to request a certificate from a provisioning
* server
*/
- public final static class ProvisionRequest {
+ public static final class ProvisionRequest {
ProvisionRequest() {}
/**
@@ -812,7 +1006,7 @@
*
* @hide - not part of the public API at this time
*/
- public final static class CertificateRequest {
+ public static final class CertificateRequest {
private byte[] mData;
private String mDefaultUrl;
@@ -860,7 +1054,7 @@
*
* @hide - not part of the public API at this time
*/
- public final static class Certificate {
+ public static final class Certificate {
Certificate() {}
/**
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 0c1c7e9..726622f 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -439,6 +439,22 @@
public static final String KEY_PRIORITY = "priority";
/**
+ * A key describing the desired operating frame rate for video or sample rate for audio
+ * that the codec will need to operate at.
+ * <p>
+ * The associated value is an integer or a float representing frames-per-second or
+ * samples-per-second
+ * <p>
+ * This is used for cases like high-speed/slow-motion video capture, where the video encoder
+ * format contains the target playback rate (e.g. 30fps), but the component must be able to
+ * handle the high operating capture rate (e.g. 240fps).
+ * <p>
+ * This rate will be used by codec for resource planning and setting the operating points.
+ *
+ */
+ public static final String KEY_OPERATING_RATE = "operating-rate";
+
+ /**
* A key describing the desired profile to be used by an encoder.
* Constants are declared in {@link MediaCodecInfo.CodecProfileLevel}.
* This key is only supported for codecs that specify a profile.
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 9a69c06..9aa8003 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -498,5 +498,11 @@
* The video rotation angle may be 0, 90, 180, or 270 degrees.
*/
public static final int METADATA_KEY_VIDEO_ROTATION = 24;
+ /**
+ * This key retrieves the original capture framerate, if it's
+ * available. The capture framerate will be a floating point
+ * number.
+ */
+ public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25;
// Add more here...
}
diff --git a/media/java/android/media/midi/IMidiDeviceServer.aidl b/media/java/android/media/midi/IMidiDeviceServer.aidl
index 642078a..96d12fd 100644
--- a/media/java/android/media/midi/IMidiDeviceServer.aidl
+++ b/media/java/android/media/midi/IMidiDeviceServer.aidl
@@ -16,6 +16,7 @@
package android.media.midi;
+import android.media.midi.MidiDeviceInfo;
import android.os.ParcelFileDescriptor;
/** @hide */
@@ -27,4 +28,6 @@
// connects the input port pfd to the specified output port
void connectPorts(IBinder token, in ParcelFileDescriptor pfd, int outputPortNumber);
+
+ MidiDeviceInfo getDeviceInfo();
}
diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java
index 7201e25..af108eb 100644
--- a/media/java/android/media/midi/MidiDeviceInfo.java
+++ b/media/java/android/media/midi/MidiDeviceInfo.java
@@ -237,29 +237,23 @@
}
/**
- * Returns information about an input port.
+ * Returns information about the device's ports.
+ * The ports are in unspecified order.
*
- * @param portNumber the number of the input port
- * @return the input port's information object
+ * @return array of {@link PortInfo}
*/
- public PortInfo getInputPortInfo(int portNumber) {
- if (portNumber < 0 || portNumber >= mInputPortCount) {
- throw new IllegalArgumentException("portNumber out of range");
- }
- return new PortInfo(PortInfo.TYPE_INPUT, portNumber, mInputPortNames[portNumber]);
- }
+ public PortInfo[] getPortList() {
+ PortInfo[] portInfoList = new PortInfo[mInputPortCount + mOutputPortCount];
- /**
- * Returns information about an output port.
- *
- * @param portNumber the number of the output port
- * @return the output port's information object
- */
- public PortInfo getOutputPortInfo(int portNumber) {
- if (portNumber < 0 || portNumber >= mOutputPortCount) {
- throw new IllegalArgumentException("portNumber out of range");
+ int index = 0;
+ for (int i = 0; i < mInputPortCount; i++) {
+ portInfoList[index++] = new PortInfo(PortInfo.TYPE_INPUT, i, mInputPortNames[i]);
}
- return new PortInfo(PortInfo.TYPE_OUTPUT, portNumber, mOutputPortNames[portNumber]);
+ for (int i = 0; i < mOutputPortCount; i++) {
+ portInfoList[index++] = new PortInfo(PortInfo.TYPE_OUTPUT, i, mOutputPortNames[i]);
+ }
+
+ return portInfoList;
}
/**
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index bc85f92..a316a44 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -252,6 +252,11 @@
mPortClients.put(token, client);
}
}
+
+ @Override
+ public MidiDeviceInfo getDeviceInfo() {
+ return mDeviceInfo;
+ }
};
/* package */ MidiDeviceServer(IMidiManager midiManager, MidiReceiver[] inputPortReceivers,
@@ -279,6 +284,10 @@
return mServer;
}
+ public IBinder asBinder() {
+ return mServer.asBinder();
+ }
+
/* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) {
if (mDeviceInfo != null) {
throw new IllegalStateException("setDeviceInfo should only be called once");
diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java
index 8b1de3e..ce12a4f 100644
--- a/media/java/android/media/midi/MidiDeviceService.java
+++ b/media/java/android/media/midi/MidiDeviceService.java
@@ -91,7 +91,7 @@
/**
* Returns an array of {@link MidiReceiver} for the device's input ports.
* Subclasses must override this to provide the receivers which will receive
- * data sent to the device's input ports. An empty array or null should be returned if
+ * data sent to the device's input ports. An empty array should be returned if
* the device has no input ports.
* @return array of MidiReceivers
*/
diff --git a/media/java/android/media/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java
index 1d3b37a..ff16a57 100644
--- a/media/java/android/media/midi/MidiInputPort.java
+++ b/media/java/android/media/midi/MidiInputPort.java
@@ -83,7 +83,18 @@
if (mOutputStream == null) {
throw new IOException("MidiInputPort is closed");
}
- int length = MidiPortImpl.packMessage(msg, offset, count, timestamp, mBuffer);
+ int length = MidiPortImpl.packData(msg, offset, count, timestamp, mBuffer);
+ mOutputStream.write(mBuffer, 0, length);
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ synchronized (mBuffer) {
+ if (mOutputStream == null) {
+ throw new IOException("MidiInputPort is closed");
+ }
+ int length = MidiPortImpl.packFlush(mBuffer);
mOutputStream.write(mBuffer, 0, length);
}
}
diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java
index d62b2dc..0ba1744 100644
--- a/media/java/android/media/midi/MidiManager.java
+++ b/media/java/android/media/midi/MidiManager.java
@@ -16,6 +16,7 @@
package android.media.midi;
+import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -42,6 +43,24 @@
public final class MidiManager {
private static final String TAG = "MidiManager";
+ /**
+ * Intent for starting BluetoothMidiService
+ * @hide
+ */
+ public static final String BLUETOOTH_MIDI_SERVICE_INTENT =
+ "android.media.midi.BluetoothMidiService";
+
+ /**
+ * BluetoothMidiService package name
+ */
+ private static final String BLUETOOTH_MIDI_SERVICE_PACKAGE = "com.android.bluetoothmidiservice";
+
+ /**
+ * BluetoothMidiService class name
+ */
+ private static final String BLUETOOTH_MIDI_SERVICE_CLASS =
+ "com.android.bluetoothmidiservice.BluetoothMidiService";
+
private final Context mContext;
private final IMidiManager mService;
private final IBinder mToken = new Binder();
@@ -145,6 +164,19 @@
}
/**
+ * Callback class used for receiving the results of {@link #openBluetoothDevice}
+ */
+ abstract public static class BluetoothOpenCallback {
+ /**
+ * Called to respond to a {@link #openBluetoothDevice} request
+ *
+ * @param bluetoothDevice the {@link android.bluetooth.BluetoothDevice} to open
+ * @param device a {@link MidiDevice} for opened device, or null if opening failed
+ */
+ abstract public void onDeviceOpened(BluetoothDevice bluetoothDevice, MidiDevice device);
+ }
+
+ /**
* @hide
*/
public MidiManager(Context context, IMidiManager service) {
@@ -214,6 +246,19 @@
}
}
+ private void sendBluetoothDeviceResponse(final BluetoothDevice bluetoothDevice,
+ final MidiDevice device, final BluetoothOpenCallback callback, Handler handler) {
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override public void run() {
+ callback.onDeviceOpened(bluetoothDevice, device);
+ }
+ });
+ } else {
+ callback.onDeviceOpened(bluetoothDevice, device);
+ }
+ }
+
/**
* Opens a MIDI device for reading and writing.
*
@@ -260,7 +305,7 @@
// return immediately to avoid calling sendOpenDeviceResponse below
return;
} else {
- Log.e(TAG, "Unable to bind service: " + intent);
+ Log.e(TAG, "Unable to bind service: " + intent);
}
}
} else {
@@ -272,6 +317,51 @@
sendOpenDeviceResponse(deviceInfo, device, callback, handler);
}
+ /**
+ * Opens a Bluetooth MIDI device for reading and writing.
+ *
+ * @param bluetoothDevice a {@link android.bluetooth.BluetoothDevice} to open as a MIDI device
+ * @param callback a {@link MidiManager.BluetoothOpenCallback} to be called to receive the
+ * result
+ * @param handler the {@link android.os.Handler Handler} that will be used for delivering
+ * the result. If handler is null, then the thread used for the
+ * callback is unspecified.
+ */
+ public void openBluetoothDevice(final BluetoothDevice bluetoothDevice,
+ final BluetoothOpenCallback callback, final Handler handler) {
+ Intent intent = new Intent(BLUETOOTH_MIDI_SERVICE_INTENT);
+ intent.setComponent(new ComponentName(BLUETOOTH_MIDI_SERVICE_PACKAGE,
+ BLUETOOTH_MIDI_SERVICE_CLASS));
+ intent.putExtra("device", bluetoothDevice);
+ if (!mContext.bindService(intent,
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder binder) {
+ IMidiDeviceServer server =
+ IMidiDeviceServer.Stub.asInterface(binder);
+ try {
+ // fetch MidiDeviceInfo from the server
+ MidiDeviceInfo deviceInfo = server.getDeviceInfo();
+ MidiDevice device = new MidiDevice(deviceInfo, server, mContext, this);
+ sendBluetoothDeviceResponse(bluetoothDevice, device, callback, handler);
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception in onServiceConnected");
+ sendBluetoothDeviceResponse(bluetoothDevice, null, callback, handler);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // FIXME - anything to do here?
+ }
+ },
+ Context.BIND_AUTO_CREATE))
+ {
+ Log.e(TAG, "Unable to bind service: " + intent);
+ sendBluetoothDeviceResponse(bluetoothDevice, null, callback, handler);
+ }
+ }
+
/** @hide */
public MidiDeviceServer createDeviceServer(MidiReceiver[] inputPortReceivers,
int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java
index 0290a76..7491f3c 100644
--- a/media/java/android/media/midi/MidiOutputPort.java
+++ b/media/java/android/media/midi/MidiOutputPort.java
@@ -62,12 +62,24 @@
// FIXME - inform receivers here?
}
- int offset = MidiPortImpl.getMessageOffset(buffer, count);
- int size = MidiPortImpl.getMessageSize(buffer, count);
- long timestamp = MidiPortImpl.getMessageTimeStamp(buffer, count);
+ int packetType = MidiPortImpl.getPacketType(buffer, count);
+ switch (packetType) {
+ case MidiPortImpl.PACKET_TYPE_DATA: {
+ int offset = MidiPortImpl.getDataOffset(buffer, count);
+ int size = MidiPortImpl.getDataSize(buffer, count);
+ long timestamp = MidiPortImpl.getPacketTimestamp(buffer, count);
- // dispatch to all our receivers
- mDispatcher.sendWithTimestamp(buffer, offset, size, timestamp);
+ // dispatch to all our receivers
+ mDispatcher.sendWithTimestamp(buffer, offset, size, timestamp);
+ break;
+ }
+ case MidiPortImpl.PACKET_TYPE_FLUSH:
+ mDispatcher.flush();
+ break;
+ default:
+ Log.e(TAG, "Unknown packet type " + packetType);
+ break;
+ }
}
} catch (IOException e) {
// FIXME report I/O failure?
diff --git a/media/java/android/media/midi/MidiPortImpl.java b/media/java/android/media/midi/MidiPortImpl.java
index 5795045..16fc214 100644
--- a/media/java/android/media/midi/MidiPortImpl.java
+++ b/media/java/android/media/midi/MidiPortImpl.java
@@ -24,6 +24,16 @@
private static final String TAG = "MidiPort";
/**
+ * Packet type for data packet
+ */
+ public static final int PACKET_TYPE_DATA = 1;
+
+ /**
+ * Packet type for flush packet
+ */
+ public static final int PACKET_TYPE_FLUSH = 2;
+
+ /**
* Maximum size of a packet that can pass through our ParcelFileDescriptor.
*/
public static final int MAX_PACKET_SIZE = 1024;
@@ -34,12 +44,17 @@
private static final int TIMESTAMP_SIZE = 8;
/**
- * Maximum amount of MIDI data that can be included in a packet
+ * Data packet overhead is timestamp size plus packet type byte
*/
- public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - TIMESTAMP_SIZE;
+ private static final int DATA_PACKET_OVERHEAD = TIMESTAMP_SIZE + 1;
/**
- * Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor
+ * Maximum amount of MIDI data that can be included in a packet
+ */
+ public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - DATA_PACKET_OVERHEAD;
+
+ /**
+ * Utility function for packing MIDI data to be sent through our ParcelFileDescriptor
*
* message byte array contains variable length MIDI message.
* messageSize is size of variable length MIDI message
@@ -47,46 +62,65 @@
* dest is buffer to pack into
* returns size of packed message
*/
- public static int packMessage(byte[] message, int offset, int size, long timestamp,
+ public static int packData(byte[] message, int offset, int size, long timestamp,
byte[] dest) {
- if (size + TIMESTAMP_SIZE > MAX_PACKET_SIZE) {
- size = MAX_PACKET_SIZE - TIMESTAMP_SIZE;
+ if (size > MAX_PACKET_DATA_SIZE) {
+ size = MAX_PACKET_DATA_SIZE;
}
- // message data goes first
- System.arraycopy(message, offset, dest, 0, size);
+ int length = 0;
+ // packet type goes first
+ dest[length++] = PACKET_TYPE_DATA;
+ // data goes next
+ System.arraycopy(message, offset, dest, length, size);
+ length += size;
// followed by timestamp
for (int i = 0; i < TIMESTAMP_SIZE; i++) {
- dest[size++] = (byte)timestamp;
+ dest[length++] = (byte)timestamp;
timestamp >>= 8;
}
- return size;
+ return length;
}
/**
- * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * Utility function for packing a flush command to be sent through our ParcelFileDescriptor
+ */
+ public static int packFlush(byte[] dest) {
+ dest[0] = PACKET_TYPE_FLUSH;
+ return 1;
+ }
+
+ /**
+ * Returns the packet type (PACKET_TYPE_DATA or PACKET_TYPE_FLUSH)
+ */
+ public static int getPacketType(byte[] buffer, int bufferLength) {
+ return buffer[0];
+ }
+
+ /**
+ * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
* returns the offset of the MIDI message in packed buffer
*/
- public static int getMessageOffset(byte[] buffer, int bufferLength) {
- // message is at the beginning
- return 0;
+ public static int getDataOffset(byte[] buffer, int bufferLength) {
+ // data follows packet type byte
+ return 1;
}
/**
- * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
* returns size of MIDI data in packed buffer
*/
- public static int getMessageSize(byte[] buffer, int bufferLength) {
+ public static int getDataSize(byte[] buffer, int bufferLength) {
// message length is total buffer length minus size of the timestamp
- return bufferLength - TIMESTAMP_SIZE;
+ return bufferLength - DATA_PACKET_OVERHEAD;
}
/**
- * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
* unpacks timestamp from packed buffer
*/
- public static long getMessageTimeStamp(byte[] buffer, int bufferLength) {
+ public static long getPacketTimestamp(byte[] buffer, int bufferLength) {
// timestamp is at end of the packet
int offset = bufferLength;
long timestamp = 0;
diff --git a/media/java/android/media/midi/MidiReceiver.java b/media/java/android/media/midi/MidiReceiver.java
index 6f4c266..d069075 100644
--- a/media/java/android/media/midi/MidiReceiver.java
+++ b/media/java/android/media/midi/MidiReceiver.java
@@ -42,6 +42,13 @@
throws IOException;
/**
+ * Instructs the receiver to discard all pending events.
+ * @throws IOException
+ */
+ public void flush() throws IOException {
+ }
+
+ /**
* Returns the maximum size of a message this receiver can receive.
* Defaults to {@link java.lang.Integer#MAX_VALUE} unless overridden.
* @return maximum message size
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 3c67ea0..ee2a233 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -52,7 +52,7 @@
private static final String TAG = "TvInputManager";
static final int VIDEO_UNAVAILABLE_REASON_START = 0;
- static final int VIDEO_UNAVAILABLE_REASON_END = 3;
+ static final int VIDEO_UNAVAILABLE_REASON_END = 4;
/**
* A generic reason. Video is not available due to an unspecified error.
@@ -70,7 +70,11 @@
* Video is not available because the TV input stopped the playback temporarily to buffer more
* data.
*/
- public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = VIDEO_UNAVAILABLE_REASON_END;
+ public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3;
+ /**
+ * Video is not available because the current program is audio-only.
+ */
+ public static final int VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = VIDEO_UNAVAILABLE_REASON_END;
private static final int TIME_SHIFT_STATUS_START = 0;
private static final int TIME_SHIFT_STATUS_END = 3;
@@ -306,6 +310,7 @@
* <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
* <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
* <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
+ * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}
* </ul>
*/
public void onVideoUnavailable(Session session, int reason) {
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 4e7aaa0..343ffcb 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -450,6 +450,7 @@
* <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
* <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
* <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
+ * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}
* </ul>
* @see #notifyVideoAvailable
*/
@@ -1525,6 +1526,7 @@
* <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
* <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
* <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
+ * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}
* </ul>
*/
public void onHardwareVideoUnavailable(int reason) { }
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 42c2cd7..024ffd1 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -908,6 +908,7 @@
* <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
* <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
* <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
+ * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}
* </ul>
*/
public void onVideoUnavailable(String inputId, int reason) {
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 16758d0..71457b7 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -669,6 +669,14 @@
break;
}
+ case MediaCodec::CB_CODEC_RELEASED:
+ {
+ if (!msg->findInt32("reason", &arg2)) {
+ arg2 = MediaCodec::REASON_UNKNOWN;
+ }
+ break;
+ }
+
default:
TRESPASS();
}
diff --git a/media/jni/android_media_MediaCrypto.cpp b/media/jni/android_media_MediaCrypto.cpp
index d2216fb..a9accb02 100644
--- a/media/jni/android_media_MediaCrypto.cpp
+++ b/media/jni/android_media_MediaCrypto.cpp
@@ -140,6 +140,15 @@
return jcrypto->mCrypto;
}
+// JNI conversion utilities
+static Vector<uint8_t> JByteArrayToVector(JNIEnv *env, jbyteArray const &byteArray) {
+ Vector<uint8_t> vector;
+ size_t length = env->GetArrayLength(byteArray);
+ vector.insertAt((size_t)0, length);
+ env->GetByteArrayRegion(byteArray, 0, length, (jbyte *)vector.editArray());
+ return vector;
+}
+
} // namespace android
using namespace android;
@@ -274,6 +283,37 @@
return result ? JNI_TRUE : JNI_FALSE;
}
+static void android_media_MediaCrypto_setMediaDrmSession(
+ JNIEnv *env, jobject thiz, jbyteArray jsessionId) {
+ if (jsessionId == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ sp<ICrypto> crypto = JCrypto::GetCrypto(env, thiz);
+
+ if (crypto == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ Vector<uint8_t> sessionId(JByteArrayToVector(env, jsessionId));
+
+ status_t err = crypto->setMediaDrmSession(sessionId);
+
+ String8 msg("setMediaDrmSession failed");
+ if (err == ERROR_DRM_SESSION_NOT_OPENED) {
+ msg += ": session not opened";
+ } else if (err == ERROR_UNSUPPORTED) {
+ msg += ": not supported by this crypto scheme";
+ } else if (err == NO_INIT) {
+ msg += ": crypto plugin not initialized";
+ } else if (err != OK) {
+ msg.appendFormat(": general failure (%d)", err);
+ }
+ jniThrowException(env, "android/media/MediaCryptoException", msg.string());
+}
+
static JNINativeMethod gMethods[] = {
{ "release", "()V", (void *)android_media_MediaCrypto_release },
{ "native_init", "()V", (void *)android_media_MediaCrypto_native_init },
@@ -289,6 +329,9 @@
{ "requiresSecureDecoderComponent", "(Ljava/lang/String;)Z",
(void *)android_media_MediaCrypto_requiresSecureDecoderComponent },
+
+ { "setMediaDrmSession", "([B)V",
+ (void *)android_media_MediaCrypto_setMediaDrmSession },
};
int register_android_media_Crypto(JNIEnv *env) {
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index 96d7133..f8146a7 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -96,6 +96,12 @@
jint kEventSessionReclaimed;
} gEventTypes;
+struct EventWhat {
+ jint kWhatDrmEvent;
+ jint kWhatExpirationUpdate;
+ jint kWhatKeysChange;
+} gEventWhat;
+
struct KeyTypes {
jint kKeyTypeStreaming;
jint kKeyTypeOffline;
@@ -186,25 +192,37 @@
void JNIDrmListener::notify(DrmPlugin::EventType eventType, int extra,
const Parcel *obj)
{
- jint jeventType;
+ jint jwhat;
+ jint jeventType = 0;
// translate DrmPlugin event types into their java equivalents
switch (eventType) {
case DrmPlugin::kDrmPluginEventProvisionRequired:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventProvisionRequired;
break;
case DrmPlugin::kDrmPluginEventKeyNeeded:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventKeyRequired;
break;
case DrmPlugin::kDrmPluginEventKeyExpired:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventKeyExpired;
break;
case DrmPlugin::kDrmPluginEventVendorDefined:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventVendorDefined;
break;
case DrmPlugin::kDrmPluginEventSessionReclaimed:
+ jwhat = gEventWhat.kWhatDrmEvent;
jeventType = gEventTypes.kEventSessionReclaimed;
break;
+ case DrmPlugin::kDrmPluginEventExpirationUpdate:
+ jwhat = gEventWhat.kWhatExpirationUpdate;
+ break;
+ case DrmPlugin::kDrmPluginEventKeysChange:
+ jwhat = gEventWhat.kWhatKeysChange;
+ break;
default:
ALOGE("Invalid event DrmPlugin::EventType %d, ignored", (int)eventType);
return;
@@ -217,7 +235,7 @@
Parcel* nativeParcel = parcelForJavaObject(env, jParcel);
nativeParcel->setData(obj->data(), obj->dataSize());
env->CallStaticVoidMethod(mClass, gFields.post_event, mObject,
- jeventType, extra, jParcel);
+ jwhat, jeventType, extra, jParcel);
env->DeleteLocalRef(jParcel);
}
}
@@ -573,7 +591,7 @@
FIND_CLASS(clazz, "android/media/MediaDrm");
GET_FIELD_ID(gFields.context, clazz, "mNativeContext", "J");
GET_STATIC_METHOD_ID(gFields.post_event, clazz, "postEventFromNative",
- "(Ljava/lang/Object;IILjava/lang/Object;)V");
+ "(Ljava/lang/Object;IIILjava/lang/Object;)V");
jfieldID field;
GET_STATIC_FIELD_ID(field, clazz, "EVENT_PROVISION_REQUIRED", "I");
@@ -587,6 +605,13 @@
GET_STATIC_FIELD_ID(field, clazz, "EVENT_SESSION_RECLAIMED", "I");
gEventTypes.kEventSessionReclaimed = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "DRM_EVENT", "I");
+ gEventWhat.kWhatDrmEvent = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "EXPIRATION_UPDATE", "I");
+ gEventWhat.kWhatExpirationUpdate = env->GetStaticIntField(clazz, field);
+ GET_STATIC_FIELD_ID(field, clazz, "KEYS_CHANGE", "I");
+ gEventWhat.kWhatKeysChange = env->GetStaticIntField(clazz, field);
+
GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_STREAMING", "I");
gKeyTypes.kKeyTypeStreaming = env->GetStaticIntField(clazz, field);
GET_STATIC_FIELD_ID(field, clazz, "KEY_TYPE_OFFLINE", "I");
@@ -837,7 +862,7 @@
env->SetIntField(keyObj, gFields.keyRequest.requestType,
gKeyRequestTypes.kKeyRequestTypeRelease);
break;
- case DrmPlugin::kKeyRequestType_Unknown:
+ default:
throwStateException(env, "DRM plugin failure: unknown key request type",
ERROR_DRM_UNKNOWN);
break;
diff --git a/media/packages/BluetoothMidiService/Android.mk b/media/packages/BluetoothMidiService/Android.mk
new file mode 100644
index 0000000..2c9c3c5
--- /dev/null
+++ b/media/packages/BluetoothMidiService/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := BluetoothMidiService
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/media/packages/BluetoothMidiService/AndroidManifest.xml b/media/packages/BluetoothMidiService/AndroidManifest.xml
new file mode 100644
index 0000000..15aa581
--- /dev/null
+++ b/media/packages/BluetoothMidiService/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.bluetoothmidiservice"
+ >
+
+ <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
+ <uses-feature android:name="android.software.midi" android:required="true"/>
+ <uses-permission android:name="android.permission.BLUETOOTH"/>
+
+ <application
+ android:label="@string/app_name">
+ <service android:name="BluetoothMidiService">
+ <intent-filter>
+ <action android:name="android.media.midi.BluetoothMidiService" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/media/packages/BluetoothMidiService/res/values/strings.xml b/media/packages/BluetoothMidiService/res/values/strings.xml
new file mode 100644
index 0000000..c98e56c
--- /dev/null
+++ b/media/packages/BluetoothMidiService/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+ <string name="app_name">Bluetooth MIDI Service</string>
+</resources>
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
new file mode 100644
index 0000000..8d194e5
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -0,0 +1,276 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.media.midi.MidiReceiver;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiDeviceServer;
+import android.media.midi.MidiDeviceInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.internal.midi.MidiEventScheduler;
+import com.android.internal.midi.MidiEventScheduler.MidiEvent;
+
+import libcore.io.IoUtils;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Class used to implement a Bluetooth MIDI device.
+ */
+public final class BluetoothMidiDevice {
+
+ private static final String TAG = "BluetoothMidiDevice";
+
+ private static final int MAX_PACKET_SIZE = 20;
+
+ // Bluetooth MIDI Gatt service UUID
+ private static final UUID MIDI_SERVICE = UUID.fromString(
+ "03B80E5A-EDE8-4B33-A751-6CE34EC4C700");
+ // Bluetooth MIDI Gatt characteristic UUID
+ private static final UUID MIDI_CHARACTERISTIC = UUID.fromString(
+ "7772E5DB-3868-4112-A1A9-F2669D106BF3");
+ // Descriptor UUID for enabling characteristic changed notifications
+ private static final UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString(
+ "00002902-0000-1000-8000-00805f9b34fb");
+
+ private final BluetoothDevice mBluetoothDevice;
+ private final BluetoothMidiService mService;
+ private final MidiManager mMidiManager;
+ private MidiReceiver mOutputReceiver;
+ private final MidiEventScheduler mEventScheduler = new MidiEventScheduler();
+
+ private MidiDeviceServer mDeviceServer;
+ private BluetoothGatt mBluetoothGatt;
+
+ private BluetoothGattCharacteristic mCharacteristic;
+
+ // PacketReceiver for receiving formatted packets from our BluetoothPacketEncoder
+ private final PacketReceiver mPacketReceiver = new PacketReceiver();
+
+ private final BluetoothPacketEncoder mPacketEncoder
+ = new BluetoothPacketEncoder(mPacketReceiver, MAX_PACKET_SIZE);
+
+ private final BluetoothPacketDecoder mPacketDecoder
+ = new BluetoothPacketDecoder(MAX_PACKET_SIZE);
+
+ private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status,
+ int newState) {
+ String intentAction;
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ Log.i(TAG, "Connected to GATT server.");
+ Log.i(TAG, "Attempting to start service discovery:" +
+ mBluetoothGatt.discoverServices());
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ Log.i(TAG, "Disconnected from GATT server.");
+ // FIXME synchronize?
+ close();
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ List<BluetoothGattService> services = mBluetoothGatt.getServices();
+ for (BluetoothGattService service : services) {
+ if (MIDI_SERVICE.equals(service.getUuid())) {
+ Log.d(TAG, "found MIDI_SERVICE");
+ List<BluetoothGattCharacteristic> characteristics
+ = service.getCharacteristics();
+ for (BluetoothGattCharacteristic characteristic : characteristics) {
+ if (MIDI_CHARACTERISTIC.equals(characteristic.getUuid())) {
+ Log.d(TAG, "found MIDI_CHARACTERISTIC");
+ mCharacteristic = characteristic;
+
+ // Specification says to read the characteristic first and then
+ // switch to receiving notifications
+ mBluetoothGatt.readCharacteristic(characteristic);
+ break;
+ }
+ }
+ break;
+ }
+ }
+ } else {
+ Log.w(TAG, "onServicesDiscovered received: " + status);
+ // FIXME - report error back to client?
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic,
+ int status) {
+ Log.d(TAG, "onCharacteristicRead " + status);
+
+ // switch to receiving notifications after initial characteristic read
+ mBluetoothGatt.setCharacteristicNotification(characteristic, true);
+
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+ CLIENT_CHARACTERISTIC_CONFIG);
+ // FIXME null check
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ mBluetoothGatt.writeDescriptor(descriptor);
+ }
+
+ @Override
+ public void onCharacteristicWrite(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic,
+ int status) {
+ Log.d(TAG, "onCharacteristicWrite " + status);
+ mPacketEncoder.writeComplete();
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+// logByteArray("Received ", characteristic.getValue(), 0,
+// characteristic.getValue().length);
+ mPacketDecoder.decodePacket(characteristic.getValue(), mOutputReceiver);
+ }
+ };
+
+ // This receives MIDI data that has already been passed through our MidiEventScheduler
+ // and has been normalized by our MidiFramer.
+
+ private class PacketReceiver implements PacketEncoder.PacketReceiver {
+ // buffers of every possible packet size
+ private final byte[][] mWriteBuffers;
+
+ public PacketReceiver() {
+ // Create buffers of every possible packet size
+ mWriteBuffers = new byte[MAX_PACKET_SIZE + 1][];
+ for (int i = 0; i <= MAX_PACKET_SIZE; i++) {
+ mWriteBuffers[i] = new byte[i];
+ }
+ }
+
+ @Override
+ public void writePacket(byte[] buffer, int count) {
+ if (mCharacteristic == null) {
+ Log.w(TAG, "not ready to send packet yet");
+ return;
+ }
+ byte[] writeBuffer = mWriteBuffers[count];
+ System.arraycopy(buffer, 0, writeBuffer, 0, count);
+ mCharacteristic.setValue(writeBuffer);
+// logByteArray("Sent ", mCharacteristic.getValue(), 0,
+// mCharacteristic.getValue().length);
+ mBluetoothGatt.writeCharacteristic(mCharacteristic);
+ }
+ }
+
+ public BluetoothMidiDevice(Context context, BluetoothDevice device,
+ BluetoothMidiService service) {
+ mBluetoothDevice = device;
+ mService = service;
+
+ mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, mGattCallback);
+
+ mMidiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
+
+ Bundle properties = new Bundle();
+ properties.putString(MidiDeviceInfo.PROPERTY_NAME, mBluetoothGatt.getDevice().getName());
+ properties.putParcelable(MidiDeviceInfo.PROPERTY_BLUETOOTH_DEVICE,
+ mBluetoothGatt.getDevice());
+
+ MidiReceiver[] inputPortReceivers = new MidiReceiver[1];
+ inputPortReceivers[0] = mEventScheduler.getReceiver();
+
+ mDeviceServer = mMidiManager.createDeviceServer(inputPortReceivers, 1,
+ null, null, properties, MidiDeviceInfo.TYPE_BLUETOOTH, null);
+
+ mOutputReceiver = mDeviceServer.getOutputPortReceivers()[0];
+
+ // This thread waits for outgoing messages from our MidiEventScheduler
+ // And forwards them to our MidiFramer to be prepared to send via Bluetooth.
+ new Thread("BluetoothMidiDevice " + mBluetoothDevice) {
+ @Override
+ public void run() {
+ while (true) {
+ MidiEvent event;
+ try {
+ event = (MidiEvent)mEventScheduler.waitNextEvent();
+ } catch (InterruptedException e) {
+ // try again
+ continue;
+ }
+ if (event == null) {
+ break;
+ }
+ try {
+ mPacketEncoder.sendWithTimestamp(event.data, 0, event.count,
+ event.getTimestamp());
+ } catch (IOException e) {
+ Log.e(TAG, "mPacketAccumulator.sendWithTimestamp failed", e);
+ }
+ mEventScheduler.addEventToPool(event);
+ }
+ Log.d(TAG, "BluetoothMidiDevice thread exit");
+ }
+ }.start();
+ }
+
+ void close() {
+ mEventScheduler.close();
+ if (mDeviceServer != null) {
+ IoUtils.closeQuietly(mDeviceServer);
+ mDeviceServer = null;
+ mService.deviceClosed(mBluetoothDevice);
+ }
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.close();
+ mBluetoothGatt = null;
+ }
+ }
+
+ public IBinder getBinder() {
+ return mDeviceServer.asBinder();
+ }
+
+ private static void logByteArray(String prefix, byte[] value, int offset, int count) {
+ StringBuilder builder = new StringBuilder(prefix);
+ for (int i = offset; i < count; i++) {
+ String hex = Integer.toHexString(value[i]);
+ int length = hex.length();
+ if (length == 1) {
+ hex = "0x" + hex;
+ } else {
+ hex = hex.substring(length - 2, length);
+ }
+ builder.append(hex);
+ if (i != value.length - 1) {
+ builder.append(", ");
+ }
+ }
+ Log.d(TAG, builder.toString());
+ }
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java
new file mode 100644
index 0000000..fbde2b4
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiService.java
@@ -0,0 +1,61 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import android.app.Service;
+import android.bluetooth.BluetoothDevice;
+import android.content.Intent;
+import android.media.midi.MidiManager;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.HashMap;
+
+public class BluetoothMidiService extends Service {
+ private static final String TAG = "BluetoothMidiService";
+
+ // BluetoothMidiDevices keyed by BluetoothDevice
+ private final HashMap<BluetoothDevice,BluetoothMidiDevice> mDeviceServerMap
+ = new HashMap<BluetoothDevice,BluetoothMidiDevice>();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (MidiManager.BLUETOOTH_MIDI_SERVICE_INTENT.equals(intent.getAction())) {
+ BluetoothDevice bluetoothDevice = (BluetoothDevice)intent.getParcelableExtra("device");
+ if (bluetoothDevice == null) {
+ Log.e(TAG, "no BluetoothDevice in onBind intent");
+ return null;
+ }
+
+ BluetoothMidiDevice device;
+ synchronized (mDeviceServerMap) {
+ device = mDeviceServerMap.get(bluetoothDevice);
+ if (device == null) {
+ device = new BluetoothMidiDevice(this, bluetoothDevice, this);
+ }
+ }
+ return device.getBinder();
+ }
+ return null;
+ }
+
+ void deviceClosed(BluetoothDevice device) {
+ synchronized (mDeviceServerMap) {
+ mDeviceServerMap.remove(device);
+ }
+ }
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java
new file mode 100644
index 0000000..c5bfb5f
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java
@@ -0,0 +1,106 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import android.media.midi.MidiReceiver;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * This is an abstract base class that decodes a packet buffer and passes it to a
+ * {@link android.media.midi.MidiReceiver}
+ */
+public class BluetoothPacketDecoder extends PacketDecoder {
+
+ private static final String TAG = "BluetoothPacketDecoder";
+
+ private final byte[] mBuffer;
+
+ private final int TIMESTAMP_MASK_HIGH = 0x1F80;
+ private final int TIMESTAMP_MASK_LOW = 0x7F;
+ private final int HEADER_TIMESTAMP_MASK = 0x3F;
+
+ public BluetoothPacketDecoder(int maxPacketSize) {
+ mBuffer = new byte[maxPacketSize];
+ }
+
+ @Override
+ public void decodePacket(byte[] buffer, MidiReceiver receiver) {
+ int length = buffer.length;
+
+ // NOTE his code allows running status across packets,
+ // although the specification does not allow that.
+
+ if (length < 1) {
+ Log.e(TAG, "empty packet");
+ return;
+ }
+ byte header = buffer[0];
+ if ((header & 0xC0) != 0x80) {
+ Log.e(TAG, "packet does not start with header");
+ return;
+ }
+
+ // shift bits 0 - 5 to bits 7 - 12
+ int timestamp = (header & HEADER_TIMESTAMP_MASK) << 7;
+ boolean lastWasTimestamp = false;
+ int dataCount = 0;
+ int previousLowTimestamp = 0;
+
+ // iterate through the rest of the packet, separating MIDI data from timestamps
+ for (int i = 1; i < buffer.length; i++) {
+ byte b = buffer[i];
+
+ if ((b & 0x80) != 0 && !lastWasTimestamp) {
+ lastWasTimestamp = true;
+ int lowTimestamp = b & TIMESTAMP_MASK_LOW;
+ int newTimestamp = (timestamp & TIMESTAMP_MASK_HIGH) | lowTimestamp;
+ if (lowTimestamp < previousLowTimestamp) {
+ newTimestamp = (newTimestamp + 0x0080) & TIMESTAMP_MASK_HIGH;
+ }
+ previousLowTimestamp = lowTimestamp;
+
+ if (newTimestamp != timestamp) {
+ if (dataCount > 0) {
+ // send previous message separately since it has a different timestamp
+ try {
+ // FIXME use sendWithTimestamp
+ receiver.send(mBuffer, 0, dataCount);
+ } catch (IOException e) {
+ // ???
+ }
+ dataCount = 0;
+ }
+ }
+ timestamp = newTimestamp;
+ } else {
+ lastWasTimestamp = false;
+ mBuffer[dataCount++] = b;
+ }
+ }
+
+ if (dataCount > 0) {
+ try {
+ // FIXME use sendWithTimestamp
+ receiver.send(mBuffer, 0, dataCount);
+ } catch (IOException e) {
+ // ???
+ }
+ }
+ }
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java
new file mode 100644
index 0000000..463edcf
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java
@@ -0,0 +1,157 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import android.media.midi.MidiReceiver;
+
+import com.android.internal.midi.MidiConstants;
+import com.android.internal.midi.MidiFramer;
+
+import java.io.IOException;
+
+/**
+ * This class accumulates MIDI messages to form a MIDI packet.
+ */
+public class BluetoothPacketEncoder extends PacketEncoder {
+
+ private static final String TAG = "BluetoothPacketEncoder";
+
+ private static final long MILLISECOND_NANOS = 1000000L;
+
+ // mask for generating 13 bit timestamps
+ private static final int MILLISECOND_MASK = 0x1FFF;
+
+ private final PacketReceiver mPacketReceiver;
+
+ // buffer for accumulating messages to write
+ private final byte[] mAccumulationBuffer;
+ // number of bytes currently in mAccumulationBuffer
+ private int mAccumulatedBytes;
+ // timestamp for first message in current packet
+ private int mPacketTimestamp;
+ // current running status, or zero if none
+ private int mRunningStatus;
+
+ private boolean mWritePending;
+
+ private final Object mLock = new Object();
+
+ // This receives normalized data from mMidiFramer and accumulates it into a packet buffer
+ private final MidiReceiver mFramedDataReceiver = new MidiReceiver() {
+ @Override
+ public void onReceive(byte[] msg, int offset, int count, long timestamp)
+ throws IOException {
+
+ int milliTimestamp = (int)(timestamp / MILLISECOND_NANOS) & MILLISECOND_MASK;
+ int status = msg[0] & 0xFF;
+
+ synchronized (mLock) {
+ boolean needsTimestamp = (milliTimestamp != mPacketTimestamp);
+ int bytesNeeded = count;
+ if (needsTimestamp) bytesNeeded++; // add one for timestamp byte
+ if (status == mRunningStatus) bytesNeeded--; // subtract one for status byte
+
+ if (mAccumulatedBytes + bytesNeeded > mAccumulationBuffer.length) {
+ // write out our data if there is no more room
+ // if necessary, block until previous packet is sent
+ flushLocked(true);
+ }
+
+ // write header if we are starting a new packet
+ if (mAccumulatedBytes == 0) {
+ // header byte with timestamp bits 7 - 12
+ mAccumulationBuffer[mAccumulatedBytes++] = (byte)(0x80 | (milliTimestamp >> 7));
+ mPacketTimestamp = milliTimestamp;
+ needsTimestamp = true;
+ }
+
+ // write new timestamp byte and status byte if necessary
+ if (needsTimestamp) {
+ // timestamp byte with bits 0 - 6 of timestamp
+ mAccumulationBuffer[mAccumulatedBytes++] =
+ (byte)(0x80 | (milliTimestamp & 0x7F));
+ mPacketTimestamp = milliTimestamp;
+ }
+
+ if (status != mRunningStatus) {
+ mAccumulationBuffer[mAccumulatedBytes++] = (byte)status;
+ if (MidiConstants.allowRunningStatus(status)) {
+ mRunningStatus = status;
+ } else if (MidiConstants.allowRunningStatus(status)) {
+ mRunningStatus = 0;
+ }
+ }
+
+ // now copy data bytes
+ int dataLength = count - 1;
+ System.arraycopy(msg, 1, mAccumulationBuffer, mAccumulatedBytes, dataLength);
+ // FIXME - handle long SysEx properly
+ mAccumulatedBytes += dataLength;
+
+ // write the packet if possible, but do not block
+ flushLocked(false);
+ }
+ }
+ };
+
+ // MidiFramer for normalizing incoming data
+ private final MidiFramer mMidiFramer = new MidiFramer(mFramedDataReceiver);
+
+ public BluetoothPacketEncoder(PacketReceiver packetReceiver, int maxPacketSize) {
+ mPacketReceiver = packetReceiver;
+ mAccumulationBuffer = new byte[maxPacketSize];
+ }
+
+ @Override
+ public void onReceive(byte[] msg, int offset, int count, long timestamp)
+ throws IOException {
+ // normalize the data by passing it through a MidiFramer first
+ mMidiFramer.sendWithTimestamp(msg, offset, count, timestamp);
+ }
+
+ @Override
+ public void writeComplete() {
+ synchronized (mLock) {
+ mWritePending = false;
+ flushLocked(false);
+ mLock.notify();
+ }
+ }
+
+ private void flushLocked(boolean canBlock) {
+ if (mWritePending && !canBlock) {
+ return;
+ }
+
+ while (mWritePending && mAccumulatedBytes > 0) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ // try again
+ continue;
+ }
+ }
+
+ if (mAccumulatedBytes > 0) {
+ mPacketReceiver.writePacket(mAccumulationBuffer, mAccumulatedBytes);
+ mAccumulatedBytes = 0;
+ mPacketTimestamp = 0;
+ mRunningStatus = 0;
+ mWritePending = true;
+ }
+ }
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketDecoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketDecoder.java
new file mode 100644
index 0000000..da4b63a
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketDecoder.java
@@ -0,0 +1,33 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import android.media.midi.MidiReceiver;
+
+/**
+ * This is an abstract base class that decodes a packet buffer and passes it to a
+ * {@link android.media.midi.MidiReceiver}
+ */
+public abstract class PacketDecoder {
+
+ /**
+ * Decodes MIDI data in a packet and passes it to a {@link android.media.midi.MidiReceiver}
+ * @param buffer the packet to decode
+ * @param receiver the {@link android.media.midi.MidiReceiver} to receive the decoded MIDI data
+ */
+ abstract public void decodePacket(byte[] buffer, MidiReceiver receiver);
+}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java
new file mode 100644
index 0000000..12c8b9b
--- /dev/null
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/PacketEncoder.java
@@ -0,0 +1,41 @@
+/*
+ * 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.bluetoothmidiservice;
+
+import android.media.midi.MidiReceiver;
+
+/**
+ * This is an abstract base class that encodes MIDI data into a packet buffer.
+ * PacketEncoder receives data via its {@link android.media.midi.MidiReceiver#onReceive} method
+ * and notifies its client of packets to write via the {@link PacketEncoder.PacketReceiver}
+ * interface.
+ */
+public abstract class PacketEncoder extends MidiReceiver {
+
+ public interface PacketReceiver {
+ /** Called to write an accumulated packet.
+ * @param buffer the packet buffer to write
+ * @param count the number of bytes in the packet buffer to write
+ */
+ public void writePacket(byte[] buffer, int count);
+ }
+
+ /**
+ * Called to inform PacketEncoder when the previous write is complete.
+ */
+ abstract public void writeComplete();
+}
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 310ccf0..3ca239a 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -123,5 +123,7 @@
<item quantity="one">Copying <xliff:g id="count" example="1">%1$d</xliff:g> file.</item>
<item quantity="other">Copying <xliff:g id="count" example="3">%1$d</xliff:g> files.</item>
</plurals>
+ <!-- Text shown on the copy notification while DocumentsUI performs setup in preparation for copying files [CHAR LIMIT=32] -->
+ <string name="copy_preparing">Preparing for copy\u2026</string>
</resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index f135af49b..c826aba 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -16,19 +16,24 @@
package com.android.documentsui;
+import static com.android.documentsui.model.DocumentInfo.getCursorLong;
+import static com.android.documentsui.model.DocumentInfo.getCursorString;
+
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.content.ContentResolver;
+import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
+import android.database.Cursor;
import android.net.Uri;
import android.os.CancellationSignal;
-import android.os.Environment;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
import android.text.format.DateUtils;
import android.util.Log;
@@ -36,12 +41,13 @@
import libcore.io.IoUtils;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.NumberFormat;
import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
public class CopyService extends IntentService {
public static final String TAG = "CopyService";
@@ -56,6 +62,7 @@
private volatile boolean mIsCancelled;
// Parameters of the copy job. Requests to an IntentService are serialized so this code only
// needs to deal with one job at a time.
+ private final List<Uri> mFailedFiles;
private long mBatchSize;
private long mBytesCopied;
private long mStartTime;
@@ -65,9 +72,15 @@
private long mSampleTime;
private long mSpeed;
private long mRemainingTime;
+ // Provider clients are acquired for the duration of each copy job. Note that there is an
+ // implicit assumption that all srcs come from the same authority.
+ private ContentProviderClient mSrcClient;
+ private ContentProviderClient mDstClient;
public CopyService() {
super("CopyService");
+
+ mFailedFiles = new ArrayList<Uri>();
}
@Override
@@ -88,27 +101,34 @@
ArrayList<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST);
Uri destinationUri = intent.getData();
- setupCopyJob(srcs, destinationUri);
+ try {
+ // Acquire content providers.
+ mSrcClient = DocumentsApplication.acquireUnstableProviderOrThrow(getContentResolver(),
+ srcs.get(0).authority);
+ mDstClient = DocumentsApplication.acquireUnstableProviderOrThrow(getContentResolver(),
+ destinationUri.getAuthority());
- ArrayList<String> failedFilenames = new ArrayList<String>();
- for (int i = 0; i < srcs.size() && !mIsCancelled; ++i) {
- DocumentInfo src = srcs.get(i);
- try {
- copyFile(src, destinationUri);
- } catch (IOException e) {
- Log.e(TAG, "Failed to copy " + src.displayName, e);
- failedFilenames.add(src.displayName);
+ setupCopyJob(srcs, destinationUri);
+
+ for (int i = 0; i < srcs.size() && !mIsCancelled; ++i) {
+ copy(srcs.get(i), destinationUri);
}
+ } catch (Exception e) {
+ // Catch-all to prevent any copy errors from wedging the app.
+ Log.e(TAG, "Exceptions occurred during copying", e);
+ } finally {
+ ContentProviderClient.releaseQuietly(mSrcClient);
+ ContentProviderClient.releaseQuietly(mDstClient);
+
+ // Dismiss the ongoing copy notification when the copy is done.
+ mNotificationManager.cancel(mJobId, 0);
+
+ if (mFailedFiles.size() > 0) {
+ // TODO: Display a notification when an error has occurred.
+ }
+
+ // TODO: Display a toast if the copy was cancelled.
}
-
- if (failedFilenames.size() > 0) {
- // TODO: Display a notification when an error has occurred.
- }
-
- // Dismiss the ongoing copy notification when the copy is done.
- mNotificationManager.cancel(mJobId, 0);
-
- // TODO: Display a toast if the copy was cancelled.
}
@Override
@@ -123,8 +143,10 @@
*
* @param srcs A list of src files to copy.
* @param destinationUri The URI of the destination directory.
+ * @throws RemoteException
*/
- private void setupCopyJob(ArrayList<DocumentInfo> srcs, Uri destinationUri) {
+ private void setupCopyJob(ArrayList<DocumentInfo> srcs, Uri destinationUri)
+ throws RemoteException {
// Create an ID for this copy job. Use the timestamp.
mJobId = String.valueOf(SystemClock.elapsedRealtime());
// Reset the cancellation flag.
@@ -144,13 +166,13 @@
// TODO: Add a content intent to open the destination folder.
// Send an initial progress notification.
+ mProgressBuilder.setProgress(0, 0, true); // Indeterminate progress while setting up.
+ mProgressBuilder.setContentText(getString(R.string.copy_preparing));
mNotificationManager.notify(mJobId, 0, mProgressBuilder.build());
// Reset batch parameters.
- mBatchSize = 0;
- for (DocumentInfo doc : srcs) {
- mBatchSize += doc.size;
- }
+ mFailedFiles.clear();
+ mBatchSize = calculateFileSizes(srcs);
mBytesCopied = 0;
mStartTime = SystemClock.elapsedRealtime();
mLastNotificationTime = 0;
@@ -165,6 +187,66 @@
}
/**
+ * Calculates the cumulative size of all the documents in the list. Directories are recursed
+ * into and totaled up.
+ *
+ * @param srcs
+ * @return Size in bytes.
+ * @throws RemoteException
+ */
+ private long calculateFileSizes(List<DocumentInfo> srcs) throws RemoteException {
+ long result = 0;
+ for (DocumentInfo src : srcs) {
+ if (Document.MIME_TYPE_DIR.equals(src.mimeType)) {
+ // Directories need to be recursed into.
+ result += calculateFileSizesHelper(src.derivedUri);
+ } else {
+ result += src.size;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Calculates (recursively) the cumulative size of all the files under the given directory.
+ *
+ * @throws RemoteException
+ */
+ private long calculateFileSizesHelper(Uri uri) throws RemoteException {
+ final String authority = uri.getAuthority();
+ final Uri queryUri = DocumentsContract.buildChildDocumentsUri(authority,
+ DocumentsContract.getDocumentId(uri));
+ final String queryColumns[] = new String[] {
+ Document.COLUMN_DOCUMENT_ID,
+ Document.COLUMN_MIME_TYPE,
+ Document.COLUMN_SIZE
+ };
+
+ long result = 0;
+ Cursor cursor = null;
+ try {
+ cursor = mSrcClient.query(queryUri, queryColumns, null, null, null);
+ while (cursor.moveToNext()) {
+ if (Document.MIME_TYPE_DIR.equals(
+ getCursorString(cursor, Document.COLUMN_MIME_TYPE))) {
+ // Recurse into directories.
+ final Uri subdirUri = DocumentsContract.buildDocumentUri(authority,
+ getCursorString(cursor, Document.COLUMN_DOCUMENT_ID));
+ result += calculateFileSizesHelper(subdirUri);
+ } else {
+ // This may return -1 if the size isn't defined. Ignore those cases.
+ long size = getCursorLong(cursor, Document.COLUMN_SIZE);
+ result += size > 0 ? size : 0;
+ }
+ }
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ }
+
+ return result;
+ }
+
+ /**
* Cancels the current copy job, if its ID matches the given ID.
*
* @param intent The cancellation intent.
@@ -173,7 +255,7 @@
final String cancelledId = intent.getStringExtra(EXTRA_CANCEL);
// Do nothing if the cancelled ID doesn't match the current job ID. This prevents racey
// cancellation requests from affecting unrelated copy jobs.
- if (java.util.Objects.equals(mJobId, cancelledId)) {
+ if (Objects.equals(mJobId, cancelledId)) {
// Set the cancel flag. This causes the copy loops to exit.
mIsCancelled = true;
// Dismiss the progress notification here rather than in the copy loop. This preserves
@@ -237,21 +319,78 @@
}
/**
- * Copies a file to a given location.
+ * Copies a the given documents to the given location.
*
- * @param srcInfo The source file.
- * @param destinationUri The URI of the destination directory.
- * @throws IOException
+ * @param srcInfo DocumentInfos for the documents to copy.
+ * @param dstDirUri The URI of the destination directory.
+ * @throws RemoteException
*/
- private void copyFile(DocumentInfo srcInfo, Uri destinationUri) throws IOException {
- final Context context = getApplicationContext();
- final ContentResolver resolver = context.getContentResolver();
-
- final Uri writableDstUri = DocumentsContract.buildDocumentUriUsingTree(destinationUri,
- DocumentsContract.getTreeDocumentId(destinationUri));
- final Uri dstFileUri = DocumentsContract.createDocument(resolver, writableDstUri,
+ private void copy(DocumentInfo srcInfo, Uri dstDirUri) throws RemoteException {
+ final Uri dstUri = DocumentsContract.createDocument(mDstClient, dstDirUri,
srcInfo.mimeType, srcInfo.displayName);
+ if (dstUri == null) {
+ // If this is a directory, the entire subdir will not be copied over.
+ Log.e(TAG, "Error while copying " + srcInfo.displayName);
+ mFailedFiles.add(srcInfo.derivedUri);
+ return;
+ }
+ if (Document.MIME_TYPE_DIR.equals(srcInfo.mimeType)) {
+ copyDirectoryHelper(srcInfo.derivedUri, dstUri);
+ } else {
+ copyFileHelper(srcInfo.derivedUri, dstUri);
+ }
+ }
+
+ /**
+ * Handles recursion into a directory and copying its contents. Note that in linux terms, this
+ * does the equivalent of "cp src/* dst", not "cp -r src dst".
+ *
+ * @param srcDirUri URI of the directory to copy from. The routine will copy the directory's
+ * contents, not the directory itself.
+ * @param dstDirUri URI of the directory to copy to. Must be created beforehand.
+ * @throws RemoteException
+ */
+ private void copyDirectoryHelper(Uri srcDirUri, Uri dstDirUri) throws RemoteException {
+ // Recurse into directories. Copy children into the new subdirectory.
+ final String queryColumns[] = new String[] {
+ Document.COLUMN_DISPLAY_NAME,
+ Document.COLUMN_DOCUMENT_ID,
+ Document.COLUMN_MIME_TYPE,
+ Document.COLUMN_SIZE
+ };
+ final Uri queryUri = DocumentsContract.buildChildDocumentsUri(srcDirUri.getAuthority(),
+ DocumentsContract.getDocumentId(srcDirUri));
+ Cursor cursor = null;
+ try {
+ // Iterate over srcs in the directory; copy to the destination directory.
+ cursor = mSrcClient.query(queryUri, queryColumns, null, null, null);
+ while (cursor.moveToNext()) {
+ final String childMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+ final Uri dstUri = DocumentsContract.createDocument(mDstClient, dstDirUri,
+ childMimeType, getCursorString(cursor, Document.COLUMN_DISPLAY_NAME));
+ final Uri childUri = DocumentsContract.buildDocumentUri(srcDirUri.getAuthority(),
+ getCursorString(cursor, Document.COLUMN_DOCUMENT_ID));
+ if (Document.MIME_TYPE_DIR.equals(childMimeType)) {
+ copyDirectoryHelper(childUri, dstUri);
+ } else {
+ copyFileHelper(childUri, dstUri);
+ }
+ }
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ }
+ }
+
+ /**
+ * Handles copying a single file.
+ *
+ * @param srcUri URI of the file to copy from.
+ * @param dstUri URI of the *file* to copy to. Must be created beforehand.
+ * @throws RemoteException
+ */
+ private void copyFileHelper(Uri srcUri, Uri dstUri) throws RemoteException {
+ // Copy an individual file.
CancellationSignal canceller = new CancellationSignal();
ParcelFileDescriptor srcFile = null;
ParcelFileDescriptor dstFile = null;
@@ -260,8 +399,8 @@
boolean errorOccurred = false;
try {
- srcFile = resolver.openFileDescriptor(srcInfo.derivedUri, "r", canceller);
- dstFile = resolver.openFileDescriptor(dstFileUri, "w", canceller);
+ srcFile = mSrcClient.openFile(srcUri, "r", canceller);
+ dstFile = mDstClient.openFile(dstUri, "w", canceller);
src = new ParcelFileDescriptor.AutoCloseInputStream(srcFile);
dst = new ParcelFileDescriptor.AutoCloseOutputStream(dstFile);
@@ -275,7 +414,8 @@
dstFile.checkError();
} catch (IOException e) {
errorOccurred = true;
- Log.e(TAG, "Error while copying " + srcInfo.displayName, e);
+ Log.e(TAG, "Error while copying " + srcUri.toString(), e);
+ mFailedFiles.add(srcUri);
} finally {
// This also ensures the file descriptors are closed.
IoUtils.closeQuietly(src);
@@ -285,8 +425,13 @@
if (errorOccurred || mIsCancelled) {
// Clean up half-copied files.
canceller.cancel();
- if (!DocumentsContract.deleteDocument(resolver, dstFileUri)) {
- Log.w(TAG, "Failed to clean up: " + srcInfo.displayName);
+ try {
+ DocumentsContract.deleteDocument(mDstClient, dstUri);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to clean up: " + srcUri, e);
+ // RemoteExceptions usually signal that the connection is dead, so there's no point
+ // attempting to continue. Propagate the exception up so the copy job is cancelled.
+ throw e;
}
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 83071bd..0e3016d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -357,7 +357,12 @@
return;
}
- Uri destination = data.getData();
+ // Because the destination picker is launched using an open tree intent, the URI returned is
+ // a tree URI. Convert it to a document URI.
+ // TODO: Remove this step when the destination picker returns a document URI.
+ final Uri destinationTree = data.getData();
+ final Uri destination = DocumentsContract.buildDocumentUriUsingTree(destinationTree,
+ DocumentsContract.getTreeDocumentId(destinationTree));
List<DocumentInfo> docs = mSelectedDocumentsForCopy;
Intent copyIntent = new Intent(context, CopyService.class);
@@ -506,8 +511,10 @@
open.setVisible(!manageMode);
share.setVisible(manageMode);
delete.setVisible(manageMode);
- // Hide the copy feature by default.
- copy.setVisible(SystemProperties.getBoolean("debug.documentsui.enable_copy", false));
+ // Hide the copy menu item in the recents folder. For now, also hide it by default
+ // unless the debug flag is enabled.
+ copy.setVisible((mType != TYPE_RECENT_OPEN) &&
+ SystemProperties.getBoolean("debug.documentsui.enable_copy", false));
return true;
}
@@ -575,9 +582,7 @@
if (cursor != null) {
final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
- if (!Document.MIME_TYPE_DIR.equals(docMimeType)) {
- valid = isDocumentEnabled(docMimeType, docFlags);
- }
+ valid = isDocumentEnabled(docMimeType, docFlags);
}
if (!valid) {
@@ -606,8 +611,17 @@
private void onShareDocuments(List<DocumentInfo> docs) {
Intent intent;
- if (docs.size() == 1) {
- final DocumentInfo doc = docs.get(0);
+
+ // Filter out directories - those can't be shared.
+ List<DocumentInfo> docsForSend = Lists.newArrayList();
+ for (DocumentInfo doc: docs) {
+ if (!Document.MIME_TYPE_DIR.equals(doc.mimeType)) {
+ docsForSend.add(doc);
+ }
+ }
+
+ if (docsForSend.size() == 1) {
+ final DocumentInfo doc = docsForSend.get(0);
intent = new Intent(Intent.ACTION_SEND);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -615,14 +629,14 @@
intent.setType(doc.mimeType);
intent.putExtra(Intent.EXTRA_STREAM, doc.derivedUri);
- } else if (docs.size() > 1) {
+ } else if (docsForSend.size() > 1) {
intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addCategory(Intent.CATEGORY_DEFAULT);
final ArrayList<String> mimeTypes = Lists.newArrayList();
final ArrayList<Uri> uris = Lists.newArrayList();
- for (DocumentInfo doc : docs) {
+ for (DocumentInfo doc : docsForSend) {
mimeTypes.add(doc.mimeType);
uris.add(doc.derivedUri);
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 9d16501..35e9636 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -93,6 +93,7 @@
<uses-permission android:name="android.permission.BIND_APPWIDGET" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
<uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
+ <uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS" />
<application android:label="@string/app_label">
<provider
diff --git a/packages/StatementService/Android.mk b/packages/StatementService/Android.mk
index f0adb1c..470d824 100644
--- a/packages/StatementService/Android.mk
+++ b/packages/StatementService/Android.mk
@@ -24,6 +24,8 @@
LOCAL_PACKAGE_NAME := StatementService
LOCAL_PRIVILEGED_MODULE := true
+LOCAL_JAVA_LIBRARIES += org.apache.http.legacy
+
LOCAL_STATIC_JAVA_LIBRARIES := \
libprotobuf-java-nano \
volley
diff --git a/packages/SystemUI/res/drawable/ic_volume_bt.xml b/packages/SystemUI/res/drawable/ic_volume_bt.xml
deleted file mode 100644
index bce407a..0000000
--- a/packages/SystemUI/res/drawable/ic_volume_bt.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
- 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="24dp"
- android:viewportHeight="48.0"
- android:viewportWidth="48.0"
- android:width="24dp" >
-
- <path
- android:fillColor="@color/volume_icon_color"
- android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z" />
-
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_volume_bt_mute.xml b/packages/SystemUI/res/drawable/ic_volume_bt_mute.xml
deleted file mode 100644
index 98a8137..0000000
--- a/packages/SystemUI/res/drawable/ic_volume_bt_mute.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
- 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="24dp"
- android:viewportHeight="48.0"
- android:viewportWidth="48.0"
- android:width="24dp" >
-
- <path
- android:fillColor="@color/volume_icon_color"
- android:pathData="M26.0,11.8l3.8,3.8l-3.2,3.2l2.8,2.8l6.0,-6.0L24.0,4.2l-2.0,0.0l0.0,10.1l4.0,4.0L26.0,11.8zM10.8,8.2L8.0,11.0l13.2,13.2L10.0,35.3l2.8,2.8L22.0,29.0l0.0,15.2l2.0,0.0l8.6,-8.6l4.6,4.6l2.8,-2.8L10.8,8.2zM26.0,36.5L26.0,29.0l3.8,3.8L26.0,36.5z" />
-
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_volume_media_bt.xml b/packages/SystemUI/res/drawable/ic_volume_media_bt.xml
new file mode 100644
index 0000000..3364d9c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_volume_media_bt.xml
@@ -0,0 +1,29 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+
+ <path
+ android:fillColor="@color/volume_icon_color"
+ android:pathData="M17.0,3.0l-7.0,0.0l0.0,9.3C9.5,12.1 9.0,12.0 8.5,12.0C6.0,12.0 4.0,14.0 4.0,16.5S6.0,21.0 8.5,21.0s4.5,-2.3 4.5,-4.5C13.0,14.7 13.0,6.0 13.0,6.0l4.0,0.0L17.0,3.0z"/>
+ <path
+ android:fillColor="@color/volume_icon_color"
+ android:pathData="M23.4,9.9L20.5,7.0L20.0,7.0l0.0,3.8l-2.3,-2.3L17.0,9.2l2.8,2.8L17.0,14.8l0.7,0.7l2.3,-2.3L20.0,17.0l0.5,0.0l2.8,-2.8L21.2,12.0L23.4,9.9zM21.0,8.9l0.9,0.9l-0.9,1.0L21.0,8.9zM21.9,14.2L21.0,15.1l0.0,-1.9L21.9,14.2z"/>
+
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml b/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml
new file mode 100644
index 0000000..39f54f1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml
@@ -0,0 +1,32 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0"
+ android:width="24dp" >
+
+ <path
+ android:fillColor="@color/volume_icon_color"
+ android:pathData="M13.0,6.0l4.0,0.0L17.0,3.0l-7.0,0.0l0.0,5.6l3.0,3.0C13.0,8.8 13.0,6.0 13.0,6.0z"/>
+ <path
+ android:fillColor="@color/volume_icon_color"
+ android:pathData="M2.1,5.7L8.4,12.0C6.0,12.1 4.0,14.0 4.0,16.5S6.0,21.0 8.5,21.0c2.7,0.0 4.5,-2.3 4.5,-4.3l0.0,-0.1l3.9,3.9l1.3,-1.3L3.4,4.5L2.1,5.7z"/>
+ <path
+ android:fillColor="@color/volume_icon_color"
+ android:pathData="M23.4,9.9L20.5,7.0L20.0,7.0l0.0,3.8l-2.3,-2.3L17.0,9.2l2.8,2.8L17.0,14.8l0.7,0.7l2.3,-2.3L20.0,17.0l0.5,0.0l2.8,-2.8L21.2,12.0L23.4,9.9zM21.0,8.9l0.9,0.9l-0.9,1.0L21.0,8.9zM21.9,14.2L21.0,15.1l0.0,-1.9L21.9,14.2z"/>
+
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3705157..779b55e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -981,5 +981,5 @@
<string name="volumeui_notification_text">Touch to restore the original.</string>
<!-- Volume dialog zen toggle switch title -->
- <string name="volume_zen_switch_text">Block interruptions</string>
+ <string name="volume_zen_switch_text">@*android:string/zen_mode_feature_name</string>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
new file mode 100644
index 0000000..9df67fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -0,0 +1,94 @@
+/*
+ * 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.systemui;
+
+import android.annotation.StringDef;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+
+public final class Prefs {
+ private Prefs() {} // no instantation
+
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ Key.SEARCH_APP_WIDGET_ID,
+ Key.DEBUG_MODE_ENABLED,
+ Key.HOTSPOT_TILE_LAST_USED,
+ Key.COLOR_INVERSION_TILE_LAST_USED,
+ Key.DND_TILE_VISIBLE,
+ Key.DND_TILE_COMBINED_ICON
+ })
+ public @interface Key {
+ String SEARCH_APP_WIDGET_ID = "searchAppWidgetId";
+ String DEBUG_MODE_ENABLED = "debugModeEnabled";
+ String HOTSPOT_TILE_LAST_USED = "HotspotTileLastUsed";
+ String COLOR_INVERSION_TILE_LAST_USED = "ColorInversionTileLastUsed";
+ String DND_TILE_VISIBLE = "DndTileVisible";
+ String DND_TILE_COMBINED_ICON = "DndTileCombinedIcon";
+ }
+
+ public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
+ return get(context).getBoolean(key, defaultValue);
+ }
+
+ public static void putBoolean(Context context, @Key String key, boolean value) {
+ get(context).edit().putBoolean(key, value).apply();
+ }
+
+ public static int getInt(Context context, @Key String key, int defaultValue) {
+ return get(context).getInt(key, defaultValue);
+ }
+
+ public static void putInt(Context context, @Key String key, int value) {
+ get(context).edit().putInt(key, value).apply();
+ }
+
+ public static long getLong(Context context, @Key String key, long defaultValue) {
+ return get(context).getLong(key, defaultValue);
+ }
+
+ public static void putLong(Context context, @Key String key, long value) {
+ get(context).edit().putLong(key, value).apply();
+ }
+
+ public static Map<String, ?> getAll(Context context) {
+ return get(context).getAll();
+ }
+
+ public static void remove(Context context, @Key String key) {
+ get(context).edit().remove(key).apply();
+ }
+
+ public static void registerListener(Context context,
+ OnSharedPreferenceChangeListener listener) {
+ get(context).registerOnSharedPreferenceChangeListener(listener);
+ }
+
+ public static void unregisterListener(Context context,
+ OnSharedPreferenceChangeListener listener) {
+ get(context).unregisterOnSharedPreferenceChangeListener(listener);
+ }
+
+ private static SharedPreferences get(Context context) {
+ return context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
index e60aa53..f36019b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
@@ -23,6 +23,7 @@
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.Listenable;
@@ -32,14 +33,15 @@
private final Context mContext;
private final long mTimeToShowTile;
- private final String mPrefKey;
+ @Prefs.Key private final String mPrefKey;
private final String mResetAction;
private boolean mRegistered;
- public UsageTracker(Context context, Class<?> tile, int timeoutResource) {
+ public UsageTracker(Context context, @Prefs.Key String prefKey, Class<?> tile,
+ int timeoutResource) {
mContext = context;
- mPrefKey = tile.getSimpleName() + "LastUsed";
+ mPrefKey = prefKey;
mTimeToShowTile = MILLIS_PER_DAY * mContext.getResources().getInteger(timeoutResource);
mResetAction = "com.android.systemui.qs." + tile.getSimpleName() + ".usage_reset";
}
@@ -56,16 +58,16 @@
}
public boolean isRecentlyUsed() {
- long lastUsed = getSharedPrefs().getLong(mPrefKey, 0);
+ long lastUsed = Prefs.getLong(mContext, mPrefKey, 0L /* defaultValue */);
return (System.currentTimeMillis() - lastUsed) < mTimeToShowTile;
}
public void trackUsage() {
- getSharedPrefs().edit().putLong(mPrefKey, System.currentTimeMillis()).commit();
+ Prefs.putLong(mContext, mPrefKey, System.currentTimeMillis());
}
public void reset() {
- getSharedPrefs().edit().remove(mPrefKey).commit();
+ Prefs.remove(mContext, mPrefKey);
}
public void showResetConfirmation(String title, final Runnable onConfirmed) {
@@ -87,10 +89,6 @@
d.show();
}
- private SharedPreferences getSharedPrefs() {
- return mContext.getSharedPreferences(mContext.getPackageName(), 0);
- }
-
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 5963a45..4a33f55 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -18,6 +18,7 @@
import android.provider.Settings.Secure;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.SecureSetting;
@@ -50,7 +51,8 @@
}
}
};
- mUsageTracker = new UsageTracker(host.getContext(), ColorInversionTile.class,
+ mUsageTracker = new UsageTracker(host.getContext(),
+ Prefs.Key.COLOR_INVERSION_TILE_LAST_USED, ColorInversionTile.class,
R.integer.days_to_show_color_inversion_tile);
if (mSetting.getValue() != 0 && !mUsageTracker.isRecentlyUsed()) {
mUsageTracker.trackUsage();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 64730c2..6ce63d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -29,6 +29,7 @@
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -40,8 +41,6 @@
private static final String ACTION_SET_VISIBLE = "com.android.systemui.dndtile.SET_VISIBLE";
private static final String EXTRA_VISIBLE = "visible";
- private static final String PREF_KEY_VISIBLE = "DndTileVisible";
- private static final String PREF_KEY_COMBINED_ICON = "DndTileCombinedIcon";
private final ZenModeController mController;
private final DndDetailAdapter mDetailAdapter;
@@ -57,19 +56,20 @@
}
public static void setVisible(Context context, boolean visible) {
- getSharedPrefs(context).edit().putBoolean(PREF_KEY_VISIBLE, visible).commit();
+ Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible);
}
public static boolean isVisible(Context context) {
- return getSharedPrefs(context).getBoolean(PREF_KEY_VISIBLE, false);
+ return Prefs.getBoolean(context, Prefs.Key.DND_TILE_VISIBLE, false /* defaultValue */);
}
public static void setCombinedIcon(Context context, boolean combined) {
- getSharedPrefs(context).edit().putBoolean(PREF_KEY_COMBINED_ICON, combined).commit();
+ Prefs.putBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON, combined);
}
public static boolean isCombinedIcon(Context context) {
- return getSharedPrefs(context).getBoolean(PREF_KEY_COMBINED_ICON, false);
+ return Prefs.getBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON,
+ false /* defaultValue */);
}
@Override
@@ -85,9 +85,9 @@
@Override
public void handleClick() {
if (mState.value) {
- mController.setZen(Global.ZEN_MODE_OFF);
+ mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
} else {
- mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
showDetail(true);
}
}
@@ -143,22 +143,20 @@
mListening = listening;
if (mListening) {
mController.addCallback(mZenCallback);
- getSharedPrefs(mContext).registerOnSharedPreferenceChangeListener(mPrefListener);
+ Prefs.registerListener(mContext, mPrefListener);
} else {
mController.removeCallback(mZenCallback);
- getSharedPrefs(mContext).unregisterOnSharedPreferenceChangeListener(mPrefListener);
+ Prefs.unregisterListener(mContext, mPrefListener);
}
}
- private static SharedPreferences getSharedPrefs(Context context) {
- return context.getSharedPreferences(context.getPackageName(), 0);
- }
-
private final OnSharedPreferenceChangeListener mPrefListener
= new OnSharedPreferenceChangeListener() {
@Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (PREF_KEY_COMBINED_ICON.equals(key) || PREF_KEY_VISIBLE.equals(key)) {
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+ @Prefs.Key String key) {
+ if (Prefs.Key.DND_TILE_COMBINED_ICON.equals(key) ||
+ Prefs.Key.DND_TILE_VISIBLE.equals(key)) {
refreshState();
}
}
@@ -199,7 +197,7 @@
@Override
public void setToggleState(boolean state) {
if (!state) {
- mController.setZen(Global.ZEN_MODE_OFF);
+ mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
showDetail(false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index fcc517f..6063f80 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.Intent;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.qs.UsageTracker;
import com.android.systemui.qs.QSTile;
@@ -105,7 +106,8 @@
}
private static UsageTracker newUsageTracker(Context context) {
- return new UsageTracker(context, HotspotTile.class, R.integer.days_to_show_hotspot_tile);
+ return new UsageTracker(context, Prefs.Key.HOTSPOT_TILE_LAST_USED, HotspotTile.class,
+ R.integer.days_to_show_hotspot_tile);
}
private final class Callback implements HotspotController.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 192acc6..c7f8919 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -59,8 +59,6 @@
public static class Values {
public static class App {
public static int AppWidgetHostId = 1024;
- public static String Key_SearchAppWidgetId = "searchAppWidgetId";
- public static String Key_DebugModeEnabled = "debugModeEnabled";
public static String DebugModeVersion = "A";
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 011c02e..1001feb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -25,7 +25,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -34,6 +33,8 @@
import android.view.View;
import android.view.ViewStub;
import android.widget.Toast;
+
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.misc.DebugTrigger;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
@@ -565,10 +566,9 @@
/** Called when debug mode is triggered */
public void onDebugModeTriggered() {
if (mConfig.developerOptionsEnabled) {
- SharedPreferences settings = getSharedPreferences(getPackageName(), 0);
- if (settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false)) {
+ if (Prefs.getBoolean(this, Prefs.Key.DEBUG_MODE_ENABLED, false /* boolean */)) {
// Disable the debug mode
- settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply();
+ Prefs.remove(this, Prefs.Key.DEBUG_MODE_ENABLED);
mConfig.debugModeEnabled = false;
inflateDebugOverlay();
if (mDebugOverlay != null) {
@@ -576,7 +576,7 @@
}
} else {
// Enable the debug mode
- settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply();
+ Prefs.putBoolean(this, Prefs.Key.DEBUG_MODE_ENABLED, true);
mConfig.debugModeEnabled = true;
inflateDebugOverlay();
if (mDebugOverlay != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index abeb2b0..244fada 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -18,7 +18,6 @@
import android.app.ActivityManager;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -26,6 +25,8 @@
import android.util.DisplayMetrics;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -177,12 +178,12 @@
/** Updates the state, given the specified context */
void update(Context context) {
- SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
Resources res = context.getResources();
DisplayMetrics dm = res.getDisplayMetrics();
// Debug mode
- debugModeEnabled = settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false);
+ debugModeEnabled = Prefs.getBoolean(context, Prefs.Key.DEBUG_MODE_ENABLED,
+ false /* defaultValue */);
if (debugModeEnabled) {
Console.Enabled = true;
}
@@ -206,7 +207,8 @@
// Search Bar
searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height);
- searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1);
+ searchBarAppWidgetId = Prefs.getInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID,
+ -1 /* defaultValue */);
// Task stack
taskStackScrollDuration =
@@ -280,9 +282,7 @@
/** Updates the search bar app widget */
public void updateSearchBarAppWidgetId(Context context, int appWidgetId) {
searchBarAppWidgetId = appWidgetId;
- SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
- settings.edit().putInt(Constants.Values.App.Key_SearchAppWidgetId,
- appWidgetId).apply();
+ Prefs.putInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, appWidgetId);
}
/** Updates the states that need to be re-read whenever we re-initialize. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index a43110d..7c199cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -113,6 +113,7 @@
import com.android.systemui.DemoMode;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
@@ -2577,8 +2578,7 @@
}
pw.println("SharedPreferences:");
- for (Map.Entry<String, ?> entry : mContext.getSharedPreferences(mContext.getPackageName(),
- Context.MODE_PRIVATE).getAll().entrySet()) {
+ for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) {
pw.print(" "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
index 0e21457..67cc788 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
@@ -17,16 +17,19 @@
package com.android.systemui.statusbar.policy;
import android.content.ComponentName;
+import android.net.Uri;
import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
public interface ZenModeController {
void addCallback(Callback callback);
void removeCallback(Callback callback);
- void setZen(int zen);
+ void setZen(int zen, Uri conditionId, String reason);
int getZen();
void requestConditions(boolean request);
- void setExitCondition(Condition exitCondition);
- Condition getExitCondition();
+ ZenRule getManualRule();
+ ZenModeConfig getConfig();
long getNextAlarm();
void setUserId(int userId);
boolean isZenAvailable();
@@ -35,10 +38,11 @@
public static class Callback {
public void onZenChanged(int zen) {}
- public void onExitConditionChanged(Condition exitCondition) {}
public void onConditionsChanged(Condition[] conditions) {}
public void onNextAlarmChanged() {}
public void onZenAvailableChanged(boolean available) {}
public void onEffectsSupressorChanged() {}
+ public void onManualRuleChanged(ZenRule rule) {}
+ public void onConfigChanged(ZenModeConfig config) {}
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index bea0c86..830a197 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -33,6 +33,7 @@
import android.service.notification.Condition;
import android.service.notification.IConditionListener;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
import android.util.Log;
import android.util.Slog;
@@ -40,6 +41,7 @@
import java.util.ArrayList;
import java.util.LinkedHashMap;
+import java.util.Objects;
/** Platform implementation of the zen mode controller. **/
public class ZenModeControllerImpl implements ZenModeController {
@@ -58,6 +60,7 @@
private int mUserId;
private boolean mRequesting;
private boolean mRegistered;
+ private ZenModeConfig mConfig;
public ZenModeControllerImpl(Context context, Handler handler) {
mContext = context;
@@ -70,12 +73,13 @@
mConfigSetting = new GlobalSetting(mContext, handler, Global.ZEN_MODE_CONFIG_ETAG) {
@Override
protected void handleValueChanged(int value) {
- fireExitConditionChanged();
+ updateZenModeConfig();
}
};
+ mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ mConfig = mNoMan.getZenModeConfig();
mModeSetting.setListening(true);
mConfigSetting.setListening(true);
- mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mSetupObserver = new SetupObserver(handler);
mSetupObserver.register();
@@ -97,8 +101,8 @@
}
@Override
- public void setZen(int zen) {
- mModeSetting.setValue(zen);
+ public void setZen(int zen, Uri conditionId, String reason) {
+ mNoMan.setZenMode(zen, conditionId, reason);
}
@Override
@@ -116,13 +120,13 @@
}
@Override
- public void setExitCondition(Condition exitCondition) {
- mNoMan.setZenModeCondition(exitCondition);
+ public ZenRule getManualRule() {
+ return mConfig == null ? null : mConfig.manualRule;
}
@Override
- public Condition getExitCondition() {
- return mNoMan.getZenModeCondition();
+ public ZenModeConfig getConfig() {
+ return mConfig;
}
@Override
@@ -185,11 +189,15 @@
}
}
- private void fireExitConditionChanged() {
- final Condition exitCondition = getExitCondition();
- if (DEBUG) Slog.d(TAG, "exitCondition changed: " + exitCondition);
+ private void fireManualRuleChanged(ZenRule rule) {
for (Callback cb : mCallbacks) {
- cb.onExitConditionChanged(exitCondition);
+ cb.onManualRuleChanged(rule);
+ }
+ }
+
+ private void fireConfigChanged(ZenModeConfig config) {
+ for (Callback cb : mCallbacks) {
+ cb.onConfigChanged(config);
}
}
@@ -203,6 +211,17 @@
mConditions.values().toArray(new Condition[mConditions.values().size()]));
}
+ private void updateZenModeConfig() {
+ final ZenModeConfig config = mNoMan.getZenModeConfig();
+ if (Objects.equals(config, mConfig)) return;
+ final ZenRule oldRule = mConfig != null ? mConfig.manualRule : null;
+ mConfig = config;
+ fireConfigChanged(config);
+ final ZenRule newRule = config != null ? config.manualRule : null;
+ if (Objects.equals(oldRule, newRule)) return;
+ fireManualRuleChanged(newRule);
+ }
+
private final IConditionListener mListener = new IConditionListener.Stub() {
@Override
public void onConditionsReceived(Condition[] conditions) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Util.java b/packages/SystemUI/src/com/android/systemui/volume/Util.java
index 78baf67..216a4da 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Util.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Util.java
@@ -21,7 +21,6 @@
import android.media.VolumeProvider;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.PlaybackState;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
import android.view.View;
import android.widget.TextView;
@@ -145,10 +144,6 @@
return HMMAA.format(new Date(millis));
}
- public static String getShortTime(DowntimeInfo info) {
- return ((info.endHour + 1) % 12) + ":" + (info.endMinute < 10 ? " " : "") + info.endMinute;
- }
-
public static void setText(TextView tv, CharSequence text) {
if (Objects.equals(tv.getText(), text)) return;
tv.setText(text);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index d8b3965..539fec8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -38,7 +38,6 @@
import android.os.SystemClock;
import android.provider.Settings.Global;
import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseBooleanArray;
@@ -606,14 +605,6 @@
text = mContext.getString(R.string.volume_dnd_ends_at,
Util.getShortTime(countdown));
action = mContext.getString(R.string.volume_end_now);
- } else {
- final DowntimeInfo info = ZenModeConfig.tryParseDowntimeConditionId(mState.
- exitCondition.id);
- if (info != null) {
- text = mContext.getString(R.string.volume_dnd_ends_at,
- Util.getShortTime(info));
- action = mContext.getString(R.string.volume_end_now);
- }
}
}
if (text == null) {
@@ -700,7 +691,8 @@
final int iconRes =
isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
: ss.routedToBluetooth ?
- (ss.muted ? R.drawable.ic_volume_bt_mute : R.drawable.ic_volume_bt)
+ (ss.muted ? R.drawable.ic_volume_media_bt_mute
+ : R.drawable.ic_volume_media_bt)
: mAutomute && ss.level == 0 ? row.iconMuteRes
: (ss.muted ? row.iconMuteRes : row.iconRes);
if (iconRes != row.cachedIconRes) {
@@ -712,9 +704,9 @@
}
row.iconState =
iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
- : (iconRes == R.drawable.ic_volume_bt_mute || iconRes == row.iconMuteRes)
+ : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
? Events.ICON_STATE_MUTE
- : (iconRes == R.drawable.ic_volume_bt || iconRes == row.iconRes)
+ : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes)
? Events.ICON_STATE_UNMUTE
: Events.ICON_STATE_UNKNOWN;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index 265e2c6..5bc8c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -41,6 +41,7 @@
import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
import android.util.Log;
import android.util.SparseArray;
@@ -393,8 +394,15 @@
return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
}
+ private Condition getExitCondition() {
+ final ZenModeConfig config = mNoMan.getZenModeConfig();
+ return config == null ? null
+ : config.manualRule == null ? null
+ : config.manualRule.condition;
+ }
+
private boolean updateExitConditionW() {
- final Condition exitCondition = mNoMan.getZenModeCondition();
+ final Condition exitCondition = getExitCondition();
if (Objects.equals(mState.exitCondition, exitCondition)) return false;
mState.exitCondition = exitCondition;
return true;
@@ -476,12 +484,12 @@
}
private void onSetExitConditionW(Condition condition) {
- mNoMan.setZenModeCondition(condition);
+ mNoMan.setZenMode(mState.zenMode, condition != null ? condition.id : null, TAG);
}
private void onSetZenModeW(int mode) {
if (D.BUG) Log.d(TAG, "onSetZenModeW " + mode);
- mNoMan.setZenMode(mode);
+ mNoMan.setZenMode(mode, null, TAG);
}
private void onDismissRequestedW(int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
index f99eb6d..ef8257c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
@@ -17,10 +17,11 @@
import android.animation.LayoutTransition;
import android.animation.ValueAnimator;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.provider.Settings.Global;
-import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
@@ -34,6 +35,8 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.ZenModeController;
+import java.util.Objects;
+
/**
* Switch bar + zen mode panel (conditions) attached to the bottom of the volume dialog.
*/
@@ -57,6 +60,7 @@
private TextView mSummaryLine2;
private boolean mFooterExpanded;
private int mZen = -1;
+ private ZenModeConfig mConfig;
private Callback mCallback;
public ZenFooter(Context context, AttributeSet attrs) {
@@ -102,8 +106,8 @@
setZen(zen);
}
@Override
- public void onExitConditionChanged(Condition exitCondition) {
- update();
+ public void onConfigChanged(ZenModeConfig config) {
+ setConfig(config);
}
});
mSwitchBar.setOnClickListener(new OnClickListener() {
@@ -129,6 +133,7 @@
}
});
mZen = mController.getZen();
+ mConfig = mController.getConfig();
update();
}
@@ -138,6 +143,12 @@
update();
}
+ private void setConfig(ZenModeConfig config) {
+ if (Objects.equals(mConfig, config)) return;
+ mConfig = config;
+ update();
+ }
+
public boolean isZen() {
return isZenPriority() || isZenAlarms() || isZenNone();
}
@@ -196,7 +207,9 @@
: isZenNone() ? mContext.getString(R.string.interruption_level_none)
: null;
Util.setText(mSummaryLine1, line1);
- Util.setText(mSummaryLine2, mZenModePanel.getExitConditionText());
+ final String line2 = ZenModeConfig.getConditionSummary(mContext, mConfig,
+ ActivityManager.getCurrentUser());
+ Util.setText(mSummaryLine2, line2);
}
private final OnCheckedChangeListener mCheckedListener = new OnCheckedChangeListener() {
@@ -208,7 +221,7 @@
: Global.ZEN_MODE_OFF;
mZen = newZen; // this one's optimistic
setFooterExpanded(isChecked);
- mController.setZen(newZen);
+ mController.setZen(newZen, null, TAG);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index cb6c29f..f6d4c36 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -32,6 +32,7 @@
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AttributeSet;
@@ -157,7 +158,6 @@
}
mZenButtons.getChildAt(3).setVisibility(mEmbedded ? GONE : VISIBLE);
mZenEmbeddedDivider.setVisibility(mEmbedded ? VISIBLE : GONE);
- setExpanded(mEmbedded);
updateWidgets();
}
@@ -278,7 +278,7 @@
if (expanded == mExpanded) return;
if (DEBUG) Log.d(mTag, "setExpanded " + expanded);
mExpanded = expanded;
- if (mExpanded) {
+ if (mExpanded && isShown()) {
ensureSelection();
}
updateWidgets();
@@ -299,7 +299,7 @@
});
}
if (mRequestingConditions) {
- mTimeCondition = parseExistingTimeCondition(mExitCondition);
+ mTimeCondition = parseExistingTimeCondition(mContext, mExitCondition);
if (mTimeCondition != null) {
mBucketIndex = -1;
} else {
@@ -327,10 +327,9 @@
for (int i = 0; i < mMaxConditions; i++) {
mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
}
- setExitCondition(mController.getExitCondition());
refreshExitConditionText();
mSessionZen = getSelectedZen(-1);
- handleUpdateZen(mController.getZen());
+ handleUpdateManualRule(mController.getManualRule());
if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
hideAllConditions();
mController.addCallback(mZenCallback);
@@ -352,6 +351,10 @@
return condition != null ? condition.id : null;
}
+ private Uri getRealConditionId(Condition condition) {
+ return isForever(condition) ? null : getConditionId(condition);
+ }
+
private static boolean sameConditionId(Condition lhs, Condition rhs) {
return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id);
}
@@ -360,18 +363,18 @@
return condition == null ? null : condition.copy();
}
- public String getExitConditionText() {
- return mExitConditionText;
+ private void refreshExitConditionText() {
+ mExitConditionText = getExitConditionText(mContext, mExitCondition);
}
- private void refreshExitConditionText() {
- if (mExitCondition == null) {
- mExitConditionText = foreverSummary();
- } else if (isCountdown(mExitCondition)) {
- final Condition condition = parseExistingTimeCondition(mExitCondition);
- mExitConditionText = condition != null ? condition.summary : foreverSummary();
+ public static String getExitConditionText(Context context, Condition exitCondition) {
+ if (exitCondition == null) {
+ return foreverSummary(context);
+ } else if (isCountdown(exitCondition)) {
+ final Condition condition = parseExistingTimeCondition(context, exitCondition);
+ return condition != null ? condition.summary : foreverSummary(context);
} else {
- mExitConditionText = mExitCondition.summary;
+ return exitCondition.summary;
}
}
@@ -386,9 +389,16 @@
mIconPulser.start(noneButton);
}
+ private void handleUpdateManualRule(ZenRule rule) {
+ final int zen = rule != null ? rule.zenMode : Global.ZEN_MODE_OFF;
+ handleUpdateZen(zen);
+ final Condition c = rule != null ? rule.condition : null;
+ handleExitConditionChanged(c);
+ }
+
private void handleUpdateZen(int zen) {
if (mSessionZen != -1 && mSessionZen != zen) {
- setExpanded(mEmbedded || zen != Global.ZEN_MODE_OFF);
+ setExpanded(mEmbedded && isShown() || !mEmbedded && zen != Global.ZEN_MODE_OFF);
mSessionZen = zen;
}
mZenButtons.setSelectedValue(zen);
@@ -402,6 +412,20 @@
}
}
+ private void handleExitConditionChanged(Condition exitCondition) {
+ setExitCondition(exitCondition);
+ if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
+ final int N = getVisibleConditions();
+ for (int i = 0; i < N; i++) {
+ final ConditionTag tag = getConditionTagAt(i);
+ if (tag != null) {
+ if (sameConditionId(tag.condition, mExitCondition)) {
+ bind(exitCondition, mZenConditions.getChildAt(i));
+ }
+ }
+ }
+ }
+
private Condition getSelectedCondition() {
final int N = getVisibleConditions();
for (int i = 0; i < N; i++) {
@@ -447,14 +471,14 @@
? mSubheadWarningColor : mSubheadColor);
}
- private Condition parseExistingTimeCondition(Condition condition) {
+ private static Condition parseExistingTimeCondition(Context context, Condition condition) {
if (condition == null) return null;
final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id);
if (time == 0) return null;
final long now = System.currentTimeMillis();
final long span = time - now;
if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null;
- return ZenModeConfig.toTimeCondition(mContext,
+ return ZenModeConfig.toTimeCondition(context,
time, Math.round(span / (float) MINUTES_MS), now, ActivityManager.getCurrentUser());
}
@@ -514,18 +538,18 @@
mZenConditions.getChildAt(i).setVisibility(GONE);
}
// ensure something is selected
- if (mExpanded) {
+ if (mExpanded && isShown()) {
ensureSelection();
}
}
private Condition forever() {
- return new Condition(mForeverId, foreverSummary(), "", "", 0 /*icon*/, Condition.STATE_TRUE,
- 0 /*flags*/);
+ return new Condition(mForeverId, foreverSummary(mContext), "", "", 0 /*icon*/,
+ Condition.STATE_TRUE, 0 /*flags*/);
}
- private String foreverSummary() {
- return mContext.getString(com.android.internal.R.string.zen_mode_forever);
+ private static String foreverSummary(Context context) {
+ return context.getString(com.android.internal.R.string.zen_mode_forever);
}
private ConditionTag getConditionTagAt(int index) {
@@ -574,21 +598,7 @@
}
}
- private void handleExitConditionChanged(Condition exitCondition) {
- setExitCondition(exitCondition);
- if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
- final int N = getVisibleConditions();
- for (int i = 0; i < N; i++) {
- final ConditionTag tag = getConditionTagAt(i);
- if (tag != null) {
- if (sameConditionId(tag.condition, mExitCondition)) {
- bind(exitCondition, mZenConditions.getChildAt(i));
- }
- }
- }
- }
-
- private boolean isCountdown(Condition c) {
+ private static boolean isCountdown(Condition c) {
return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
}
@@ -770,17 +780,21 @@
private void select(final Condition condition) {
if (DEBUG) Log.d(mTag, "select " + condition);
- final boolean isForever = isForever(condition);
+ if (mSessionZen == -1 || mSessionZen == Global.ZEN_MODE_OFF) {
+ if (DEBUG) Log.d(mTag, "Ignoring condition selection outside of manual zen");
+ return;
+ }
+ final Uri realConditionId = getRealConditionId(condition);
if (mController != null) {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
- mController.setExitCondition(isForever ? null : condition);
+ mController.setZen(mSessionZen, realConditionId, TAG + ".selectCondition");
}
});
}
setExitCondition(condition);
- if (isForever) {
+ if (realConditionId == null) {
mPrefs.setMinuteIndex(-1);
} else if (isCountdown(condition) && mBucketIndex != -1) {
mPrefs.setMinuteIndex(mBucketIndex);
@@ -808,24 +822,19 @@
private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
@Override
- public void onZenChanged(int zen) {
- mHandler.obtainMessage(H.UPDATE_ZEN, zen, 0).sendToTarget();
- }
- @Override
public void onConditionsChanged(Condition[] conditions) {
mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget();
}
@Override
- public void onExitConditionChanged(Condition exitCondition) {
- mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget();
+ public void onManualRuleChanged(ZenRule rule) {
+ mHandler.obtainMessage(H.MANUAL_RULE_CHANGED, rule).sendToTarget();
}
};
private final class H extends Handler {
private static final int UPDATE_CONDITIONS = 1;
- private static final int EXIT_CONDITION_CHANGED = 2;
- private static final int UPDATE_ZEN = 3;
+ private static final int MANUAL_RULE_CHANGED = 2;
private H() {
super(Looper.getMainLooper());
@@ -835,10 +844,8 @@
public void handleMessage(Message msg) {
if (msg.what == UPDATE_CONDITIONS) {
handleUpdateConditions((Condition[]) msg.obj);
- } else if (msg.what == EXIT_CONDITION_CHANGED) {
- handleExitConditionChanged((Condition) msg.obj);
- } else if (msg.what == UPDATE_ZEN) {
- handleUpdateZen(msg.arg1);
+ } else if (msg.what == MANUAL_RULE_CHANGED) {
+ handleUpdateManualRule((ZenRule) msg.obj);
}
}
}
@@ -930,12 +937,13 @@
private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
@Override
public void onSelected(final Object value) {
- if (value != null && mZenButtons.isShown()) {
+ if (value != null && mZenButtons.isShown() && isAttachedToWindow()) {
if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + value);
+ final Uri realConditionId = getRealConditionId(mSessionExitCondition);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
- mController.setZen((Integer) value);
+ mController.setZen((Integer) value, realConditionId, TAG + ".selectZen");
}
});
}
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index fd19d49..5fc1f93 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -16,6 +16,7 @@
package android.renderscript;
+import java.io.File;
import java.lang.reflect.Method;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -231,6 +232,11 @@
validate();
rsnContextSetPriority(mContext, p);
}
+ native void rsnContextSetCacheDir(long con, String cacheDir);
+ synchronized void nContextSetCacheDir(String cacheDir) {
+ validate();
+ rsnContextSetCacheDir(mContext, cacheDir);
+ }
native void rsnContextDump(long con, int bits);
synchronized void nContextDump(int bits) {
validate();
@@ -1325,6 +1331,14 @@
if (rs.mContext == 0) {
throw new RSDriverException("Failed to create RS context.");
}
+
+ // set up cache directory for entire context
+ final String CACHE_PATH = "com.android.renderscript.cache";
+ File f = new File(RenderScriptCacheDir.mCacheDir, CACHE_PATH);
+ String mCachePath = f.getAbsolutePath();
+ f.mkdirs();
+ rs.nContextSetCacheDir(mCachePath);
+
rs.mMessageThread = new MessageThread(rs);
rs.mMessageThread.start();
return rs;
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 40fad38..a40233a 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -684,6 +684,17 @@
rsContextSetPriority((RsContext)con, p);
}
+static void
+nContextSetCacheDir(JNIEnv *_env, jobject _this, jlong con, jstring cacheDir)
+{
+ AutoJavaStringToUTF8 cacheDirUTF(_env, cacheDir);
+
+ if (kLogApi) {
+ ALOGD("ContextSetCacheDir, con(%p), cacheDir(%s)", (RsContext)con, cacheDirUTF.c_str());
+ }
+ rsContextSetCacheDir((RsContext)con, cacheDirUTF.c_str(), cacheDirUTF.length());
+}
+
static void
@@ -2307,6 +2318,7 @@
{"rsnContextCreateGL", "(JIIIIIIIIIIIIFI)J", (void*)nContextCreateGL },
{"rsnContextFinish", "(J)V", (void*)nContextFinish },
{"rsnContextSetPriority", "(JI)V", (void*)nContextSetPriority },
+{"rsnContextSetCacheDir", "(JLjava/lang/String;)V", (void*)nContextSetCacheDir },
{"rsnContextSetSurface", "(JIILandroid/view/Surface;)V", (void*)nContextSetSurface },
{"rsnContextDestroy", "(J)V", (void*)nContextDestroy },
{"rsnContextDump", "(JI)V", (void*)nContextDump },
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 96840a2..1bed4f3 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -2209,7 +2209,10 @@
// Get the restore-set token for the best-available restore set for this package:
// the active set if possible, else the ancestral one. Returns zero if none available.
- long getAvailableRestoreToken(String packageName) {
+ public long getAvailableRestoreToken(String packageName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "getAvailableRestoreToken");
+
long token = mAncestralToken;
synchronized (mQueueLock) {
if (mEverStoredApps.contains(packageName)) {
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 99bbdae..5859c6a 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -317,6 +317,12 @@
}
@Override
+ public long getAvailableRestoreToken(String packageName) {
+ BackupManagerService svc = mService;
+ return (svc != null) ? svc.getAvailableRestoreToken(packageName) : 0;
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 7d156df..34e8b78 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -34,6 +34,7 @@
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
+import android.os.Process;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -61,6 +62,7 @@
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Locale;
+import java.util.Random;
import java.util.TimeZone;
import static android.app.AlarmManager.RTC_WAKEUP;
@@ -128,6 +130,7 @@
final ResultReceiver mResultReceiver = new ResultReceiver();
PendingIntent mTimeTickSender;
PendingIntent mDateChangeSender;
+ Random mRandom;
boolean mInteractive = true;
long mNonInteractiveStartTime;
long mNonInteractiveTime;
@@ -185,18 +188,20 @@
final class Batch {
long start; // These endpoints are always in ELAPSED
long end;
- boolean standalone; // certain "batches" don't participate in coalescing
+ int flags; // Flags for alarms, such as FLAG_STANDALONE.
final ArrayList<Alarm> alarms = new ArrayList<Alarm>();
Batch() {
start = 0;
end = Long.MAX_VALUE;
+ flags = 0;
}
Batch(Alarm seed) {
start = seed.whenElapsed;
- end = seed.maxWhen;
+ end = seed.maxWhenElapsed;
+ flags = seed.flags;
alarms.add(seed);
}
@@ -227,9 +232,10 @@
start = alarm.whenElapsed;
newStart = true;
}
- if (alarm.maxWhen < end) {
- end = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < end) {
+ end = alarm.maxWhenElapsed;
}
+ flags |= alarm.flags;
if (DEBUG_BATCH) {
Slog.v(TAG, " => now " + this);
@@ -241,6 +247,7 @@
boolean didRemove = false;
long newStart = 0; // recalculate endpoints as we go
long newEnd = Long.MAX_VALUE;
+ int newFlags = 0;
for (int i = 0; i < alarms.size(); ) {
Alarm alarm = alarms.get(i);
if (alarm.operation.equals(operation)) {
@@ -253,9 +260,10 @@
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
}
- if (alarm.maxWhen < newEnd) {
- newEnd = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < newEnd) {
+ newEnd = alarm.maxWhenElapsed;
}
+ newFlags |= alarm.flags;
i++;
}
}
@@ -263,6 +271,7 @@
// commit the new batch bounds
start = newStart;
end = newEnd;
+ flags = newFlags;
}
return didRemove;
}
@@ -271,6 +280,7 @@
boolean didRemove = false;
long newStart = 0; // recalculate endpoints as we go
long newEnd = Long.MAX_VALUE;
+ int newFlags = 0;
for (int i = 0; i < alarms.size(); ) {
Alarm alarm = alarms.get(i);
if (alarm.operation.getTargetPackage().equals(packageName)) {
@@ -283,9 +293,10 @@
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
}
- if (alarm.maxWhen < newEnd) {
- newEnd = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < newEnd) {
+ newEnd = alarm.maxWhenElapsed;
}
+ newFlags |= alarm.flags;
i++;
}
}
@@ -293,6 +304,7 @@
// commit the new batch bounds
start = newStart;
end = newEnd;
+ flags = newFlags;
}
return didRemove;
}
@@ -313,8 +325,8 @@
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
}
- if (alarm.maxWhen < newEnd) {
- newEnd = alarm.maxWhen;
+ if (alarm.maxWhenElapsed < newEnd) {
+ newEnd = alarm.maxWhenElapsed;
}
i++;
}
@@ -357,8 +369,9 @@
b.append(" num="); b.append(size());
b.append(" start="); b.append(start);
b.append(" end="); b.append(end);
- if (standalone) {
- b.append(" STANDALONE");
+ if (flags != 0) {
+ b.append(" flgs=0x");
+ b.append(Integer.toHexString(flags));
}
b.append('}');
return b.toString();
@@ -441,7 +454,12 @@
// minimum recurrence period or alarm futurity for us to be able to fuzz it
static final long MIN_FUZZABLE_INTERVAL = 10000;
static final BatchTimeOrder sBatchOrder = new BatchTimeOrder();
- final ArrayList<Batch> mAlarmBatches = new ArrayList<Batch>();
+ final ArrayList<Batch> mAlarmBatches = new ArrayList<>();
+
+ // set to null if in idle mode; while in this mode, any alarms we don't want
+ // to run during this time are placed in mPendingWhileIdleAlarms
+ Alarm mPendingIdleUntil = null;
+ final ArrayList<Alarm> mPendingWhileIdleAlarms = new ArrayList<>();
public AlarmManagerService(Context context) {
super(context);
@@ -486,7 +504,7 @@
final int N = mAlarmBatches.size();
for (int i = 0; i < N; i++) {
Batch b = mAlarmBatches.get(i);
- if (!b.standalone && b.canHold(whenElapsed, maxWhen)) {
+ if ((b.flags&AlarmManager.FLAG_STANDALONE) == 0 && b.canHold(whenElapsed, maxWhen)) {
return i;
}
}
@@ -503,31 +521,56 @@
void rebatchAllAlarmsLocked(boolean doValidate) {
ArrayList<Batch> oldSet = (ArrayList<Batch>) mAlarmBatches.clone();
mAlarmBatches.clear();
+ Alarm oldPendingIdleUntil = mPendingIdleUntil;
final long nowElapsed = SystemClock.elapsedRealtime();
final int oldBatches = oldSet.size();
for (int batchNum = 0; batchNum < oldBatches; batchNum++) {
Batch batch = oldSet.get(batchNum);
final int N = batch.size();
for (int i = 0; i < N; i++) {
- Alarm a = batch.get(i);
- long whenElapsed = convertToElapsed(a.when, a.type);
- final long maxElapsed;
- if (a.whenElapsed == a.maxWhen) {
- // Exact
- maxElapsed = whenElapsed;
- } else {
- // Not exact. Preserve any explicit window, otherwise recalculate
- // the window based on the alarm's new futurity. Note that this
- // reflects a policy of preferring timely to deferred delivery.
- maxElapsed = (a.windowLength > 0)
- ? (whenElapsed + a.windowLength)
- : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
- }
- setImplLocked(a.type, a.when, whenElapsed, a.windowLength, maxElapsed,
- a.repeatInterval, a.operation, batch.standalone, doValidate, a.workSource,
- a.alarmClock, a.userId);
+ reAddAlarmLocked(batch.get(i), nowElapsed, doValidate);
}
}
+ if (oldPendingIdleUntil != null && oldPendingIdleUntil != mPendingIdleUntil) {
+ Slog.wtf(TAG, "Rebatching: idle until changed from " + oldPendingIdleUntil
+ + " to " + mPendingIdleUntil);
+ if (mPendingIdleUntil == null) {
+ // Somehow we lost this... we need to restore all of the pending alarms.
+ restorePendingWhileIdleAlarmsLocked();
+ }
+ }
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+ }
+
+ void reAddAlarmLocked(Alarm a, long nowElapsed, boolean doValidate) {
+ a.when = a.origWhen;
+ long whenElapsed = convertToElapsed(a.when, a.type);
+ final long maxElapsed;
+ if (a.whenElapsed == a.maxWhenElapsed) {
+ // Exact
+ maxElapsed = whenElapsed;
+ } else {
+ // Not exact. Preserve any explicit window, otherwise recalculate
+ // the window based on the alarm's new futurity. Note that this
+ // reflects a policy of preferring timely to deferred delivery.
+ maxElapsed = (a.windowLength > 0)
+ ? (whenElapsed + a.windowLength)
+ : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
+ }
+ a.whenElapsed = whenElapsed;
+ a.maxWhenElapsed = maxElapsed;
+ setImplLocked(a, true, doValidate);
+ }
+
+ void restorePendingWhileIdleAlarmsLocked() {
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ for (int i=mPendingWhileIdleAlarms.size() - 1; i >= 0 && mPendingIdleUntil != null; i --) {
+ Alarm a = mPendingWhileIdleAlarms.remove(i);
+ reAddAlarmLocked(a, nowElapsed, false);
+ }
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
}
static final class InFlight extends Intent {
@@ -687,7 +730,7 @@
}
void setImpl(int type, long triggerAtTime, long windowLength, long interval,
- PendingIntent operation, boolean isStandalone, WorkSource workSource,
+ PendingIntent operation, int flags, WorkSource workSource,
AlarmManager.AlarmClockInfo alarmClock) {
if (operation == null) {
Slog.w(TAG, "set/setRepeating ignored because there is no intent");
@@ -745,25 +788,66 @@
Slog.v(TAG, "set(" + operation + ") : type=" + type
+ " triggerAtTime=" + triggerAtTime + " win=" + windowLength
+ " tElapsed=" + triggerElapsed + " maxElapsed=" + maxElapsed
- + " interval=" + interval + " standalone=" + isStandalone);
+ + " interval=" + interval + " flags=0x" + Integer.toHexString(flags));
}
setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
- interval, operation, isStandalone, true, workSource, alarmClock, userId);
+ interval, operation, flags, true, workSource, alarmClock, userId);
}
}
private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
- long maxWhen, long interval, PendingIntent operation, boolean isStandalone,
+ long maxWhen, long interval, PendingIntent operation, int flags,
boolean doValidate, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
int userId) {
Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
- operation, workSource, alarmClock, userId);
+ operation, workSource, flags, alarmClock, userId);
removeLocked(operation);
+ setImplLocked(a, false, doValidate);
+ }
- int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen);
+ private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) {
+ if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+ // This is a special alarm that will put the system idle until it goes off.
+ // The caller has given the time they want this to happen at, however we need
+ // to pull that earlier if there are existing alarms that have requested to
+ // bring us out of idle.
+ final int N = mAlarmBatches.size();
+ for (int i = 0; i < N; i++) {
+ Batch b = mAlarmBatches.get(i);
+ if (a.whenElapsed > b.end) {
+ // There are no interesting things happening before our idle until,
+ // so keep the requested time.
+ break;
+ }
+ if ((b.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
+ a.when = a.whenElapsed = a.maxWhenElapsed = b.end;
+ break;
+ }
+ }
+ // Add fuzz to make the alarm go off some time before the actual desired time.
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ long fuzz = fuzzForDuration(a.whenElapsed-nowElapsed);
+ if (fuzz > 0) {
+ if (mRandom == null) {
+ mRandom = new Random();
+ }
+ a.whenElapsed -= mRandom.nextLong() % fuzz;
+ }
+
+ } else if (mPendingIdleUntil != null) {
+ // We currently have an idle until alarm scheduled; if the new alarm has
+ // not explicitly stated it wants to run while idle, then put it on hold.
+ if ((a.flags&(AlarmManager.FLAG_ALLOW_WHILE_IDLE|AlarmManager.FLAG_WAKE_FROM_IDLE))
+ == 0) {
+ mPendingWhileIdleAlarms.add(a);
+ return;
+ }
+ }
+
+ int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0)
+ ? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed);
if (whichBatch < 0) {
Batch batch = new Batch(a);
- batch.standalone = isStandalone;
addBatchLocked(mAlarmBatches, batch);
} else {
Batch batch = mAlarmBatches.get(whichBatch);
@@ -775,28 +859,48 @@
}
}
- if (alarmClock != null) {
+ if (a.alarmClock != null) {
mNextAlarmClockMayChange = true;
- updateNextAlarmClockLocked();
}
- if (DEBUG_VALIDATE) {
- if (doValidate && !validateConsistencyLocked()) {
- Slog.v(TAG, "Tipping-point operation: type=" + type + " when=" + when
- + " when(hex)=" + Long.toHexString(when)
- + " whenElapsed=" + whenElapsed + " maxWhen=" + maxWhen
- + " interval=" + interval + " op=" + operation
- + " standalone=" + isStandalone);
+ boolean needRebatch = false;
+
+ if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+ mPendingIdleUntil = a;
+ needRebatch = true;
+ } else if ((a.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0 && mPendingIdleUntil != null) {
+ // If we are adding an alarm that asks to wake from idle, and we are currently
+ // idling, then we need to rebatch alarms in case the idle until time needs to
+ // be updated.
+ needRebatch = true;
+ }
+
+ if (!rebatching) {
+ if (DEBUG_VALIDATE) {
+ if (doValidate && !validateConsistencyLocked()) {
+ Slog.v(TAG, "Tipping-point operation: type=" + a.type + " when=" + a.when
+ + " when(hex)=" + Long.toHexString(a.when)
+ + " whenElapsed=" + a.whenElapsed
+ + " maxWhenElapsed=" + a.maxWhenElapsed
+ + " interval=" + a.repeatInterval + " op=" + a.operation
+ + " flags=0x" + Integer.toHexString(a.flags));
+ rebatchAllAlarmsLocked(false);
+ needRebatch = false;
+ }
+ }
+
+ if (needRebatch) {
rebatchAllAlarmsLocked(false);
}
- }
- rescheduleKernelAlarmsLocked();
+ rescheduleKernelAlarmsLocked();
+ updateNextAlarmClockLocked();
+ }
}
private final IBinder mService = new IAlarmManager.Stub() {
@Override
- public void set(int type, long triggerAtTime, long windowLength, long interval,
+ public void set(int type, long triggerAtTime, long windowLength, long interval, int flags,
PendingIntent operation, WorkSource workSource,
AlarmManager.AlarmClockInfo alarmClock) {
if (workSource != null) {
@@ -805,8 +909,17 @@
"AlarmManager.set");
}
+ if (windowLength == AlarmManager.WINDOW_EXACT) {
+ flags |= AlarmManager.FLAG_STANDALONE;
+ }
+ if (alarmClock != null) {
+ flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
+ }
+ if (Binder.getCallingUid() < Process.FIRST_APPLICATION_UID) {
+ flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE;
+ }
setImpl(type, triggerAtTime, windowLength, interval, operation,
- windowLength == AlarmManager.WINDOW_EXACT, workSource, alarmClock);
+ flags, workSource, alarmClock);
}
@Override
@@ -912,6 +1025,14 @@
dumpAlarmList(pw, b.alarms, " ", nowELAPSED, nowRTC, sdf);
}
}
+ if (mPendingIdleUntil != null) {
+ pw.println();
+ pw.println("Idle mode state:");
+ pw.print(" Idling until: "); pw.println(mPendingIdleUntil);
+ mPendingIdleUntil.dump(pw, " ", nowELAPSED, nowRTC, sdf);
+ pw.println(" Pending alarms:");
+ dumpAlarmList(pw, mPendingWhileIdleAlarms, " ", nowELAPSED, nowRTC, sdf);
+ }
pw.println();
pw.print("Past-due non-wakeup alarms: ");
@@ -1224,6 +1345,15 @@
}
void rescheduleKernelAlarmsLocked() {
+ if (mPendingIdleUntil != null) {
+ // If we have a pending "idle until" alarm, we will just blindly wait until
+ // it is time for that alarm to go off. We don't want to wake up for any
+ // other reasons.
+ mNextWakeup = mNextNonWakeup = mPendingIdleUntil.whenElapsed;
+ setLocked(ELAPSED_REALTIME_WAKEUP, mNextWakeup);
+ setLocked(ELAPSED_REALTIME, mNextNonWakeup);
+ return;
+ }
// Schedule the next upcoming wakeup alarm. If there is a deliverable batch
// prior to that which contains no wakeups, we schedule that as well.
long nextNonWakeup = 0;
@@ -1260,13 +1390,26 @@
mAlarmBatches.remove(i);
}
}
+ for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+ if (mPendingWhileIdleAlarms.get(i).operation.equals(operation)) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingWhileIdleAlarms.remove(i);
+ }
+ }
if (didRemove) {
if (DEBUG_BATCH) {
Slog.v(TAG, "remove(operation) changed bounds; rebatching");
}
+ boolean restorePending = false;
+ if (mPendingIdleUntil != null && mPendingIdleUntil.operation.equals(operation)) {
+ mPendingIdleUntil = null;
+ restorePending = true;
+ }
rebatchAllAlarmsLocked(true);
- rescheduleKernelAlarmsLocked();
+ if (restorePending) {
+ restorePendingWhileIdleAlarmsLocked();
+ }
updateNextAlarmClockLocked();
}
}
@@ -1280,6 +1423,12 @@
mAlarmBatches.remove(i);
}
}
+ for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+ if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingWhileIdleAlarms.remove(i);
+ }
+ }
if (didRemove) {
if (DEBUG_BATCH) {
@@ -1300,6 +1449,13 @@
mAlarmBatches.remove(i);
}
}
+ for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
+ if (UserHandle.getUserId(mPendingWhileIdleAlarms.get(i).operation.getCreatorUid())
+ == userHandle) {
+ // Don't set didRemove, since this doesn't impact the scheduled alarms.
+ mPendingWhileIdleAlarms.remove(i);
+ }
+ }
if (didRemove) {
if (DEBUG_BATCH) {
@@ -1344,6 +1500,11 @@
return true;
}
}
+ for (int i = 0; i < mPendingWhileIdleAlarms.size(); i++) {
+ if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+ return true;
+ }
+ }
return false;
}
@@ -1413,6 +1574,13 @@
boolean triggerAlarmsLocked(ArrayList<Alarm> triggerList, final long nowELAPSED,
final long nowRTC) {
boolean hasWakeup = false;
+ if (mPendingIdleUntil != null) {
+ // If we have a pending "idle until" alarm, don't trigger any alarms
+ // until we are past the idle period.
+ if (nowELAPSED < mPendingIdleUntil.whenElapsed) {
+ return false;
+ }
+ }
// batches are temporally sorted, so we need only pull from the
// start of the list until we either empty it or hit a batch
// that is not yet deliverable
@@ -1432,6 +1600,11 @@
Alarm alarm = batch.get(i);
alarm.count = 1;
triggerList.add(alarm);
+ if (mPendingIdleUntil == alarm) {
+ mPendingIdleUntil = null;
+ rebatchAllAlarmsLocked(false);
+ restorePendingWhileIdleAlarmsLocked();
+ }
// Recurring alarms may have passed several alarm intervals while the
// phone was asleep or off, so pass a trigger count when sending them.
@@ -1445,7 +1618,7 @@
final long nextElapsed = alarm.whenElapsed + delta;
setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
- alarm.repeatInterval, alarm.operation, batch.standalone, true,
+ alarm.repeatInterval, alarm.operation, alarm.flags, true,
alarm.workSource, alarm.alarmClock, alarm.userId);
}
@@ -1494,34 +1667,38 @@
private static class Alarm {
public final int type;
+ public final long origWhen;
public final boolean wakeup;
public final PendingIntent operation;
public final String tag;
public final WorkSource workSource;
+ public final int flags;
public int count;
public long when;
public long windowLength;
public long whenElapsed; // 'when' in the elapsed time base
- public long maxWhen; // also in the elapsed time base
+ public long maxWhenElapsed; // also in the elapsed time base
public long repeatInterval;
public final AlarmManager.AlarmClockInfo alarmClock;
public final int userId;
public PriorityClass priorityClass;
public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen,
- long _interval, PendingIntent _op, WorkSource _ws,
+ long _interval, PendingIntent _op, WorkSource _ws, int _flags,
AlarmManager.AlarmClockInfo _info, int _userId) {
type = _type;
+ origWhen = _when;
wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP
|| _type == AlarmManager.RTC_WAKEUP;
when = _when;
whenElapsed = _whenElapsed;
windowLength = _windowLength;
- maxWhen = _maxWhen;
+ maxWhenElapsed = _maxWhen;
repeatInterval = _interval;
operation = _op;
tag = makeTag(_op, _type);
workSource = _ws;
+ flags = _flags;
alarmClock = _info;
userId = _userId;
}
@@ -1561,7 +1738,8 @@
pw.println();
pw.print(prefix); pw.print("window="); pw.print(windowLength);
pw.print(" repeatInterval="); pw.print(repeatInterval);
- pw.print(" count="); pw.println(count);
+ pw.print(" count="); pw.print(count);
+ pw.print(" flags=0x"); pw.println(Integer.toHexString(flags));
pw.print(prefix); pw.print("operation="); pw.println(operation);
}
}
@@ -1599,6 +1777,20 @@
}
}
+ static long fuzzForDuration(long duration) {
+ if (duration < 15*60*1000) {
+ // If the duration until the time is less than 15 minutes, the maximum fuzz
+ // is the duration.
+ return duration;
+ } else if (duration < 90*60*1000) {
+ // If duration is less than 1 1/2 hours, the maximum fuzz is 15 minutes,
+ return 15*60*1000;
+ } else {
+ // Otherwise, we will fuzz by at most half an hour.
+ return 30*60*1000;
+ }
+ }
+
boolean checkAllowNonWakeupDelayLocked(long nowELAPSED) {
if (mInteractive) {
return false;
@@ -1886,7 +2078,7 @@
final WorkSource workSource = null; // Let system take blame for time tick events.
setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
- 0, mTimeTickSender, true, workSource, null);
+ 0, mTimeTickSender, AlarmManager.FLAG_STANDALONE, workSource, null);
}
public void scheduleDateChangedEvent() {
@@ -1899,8 +2091,8 @@
calendar.add(Calendar.DAY_OF_MONTH, 1);
final WorkSource workSource = null; // Let system take blame for date change events.
- setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource,
- null);
+ setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender,
+ AlarmManager.FLAG_STANDALONE, workSource, null);
}
}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 4677f65..d92a89f 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -15,6 +15,7 @@
package com.android.server;
+import android.annotation.NonNull;
import com.android.internal.content.PackageMonitor;
import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController;
import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
@@ -1285,13 +1286,24 @@
return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
}
- InputBindResult startInputUncheckedLocked(ClientState cs,
+ InputBindResult startInputUncheckedLocked(@NonNull ClientState cs,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
// If no method is currently selected, do nothing.
if (mCurMethodId == null) {
return mNoBinding;
}
+ if (attribute != null) {
+ // We accept an empty package name as a valid data.
+ if (!TextUtils.isEmpty(attribute.packageName) &&
+ !InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
+ attribute.packageName)) {
+ Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
+ + " uid=" + cs.uid + " package=" + attribute.packageName);
+ return mNoBinding;
+ }
+ }
+
if (mCurClient != cs) {
// Was the keyguard locked when switching over to the new client?
mCurClientInKeyguard = isKeyguardLocked();
@@ -1855,16 +1867,10 @@
}
if (mCurClient != null && mCurAttribute != null) {
- final int uid = mCurClient.uid;
- final String packageName = mCurAttribute.packageName;
- if (SystemConfig.getInstance().getFixedImeApps().contains(packageName)) {
- if (InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, uid, packageName)) {
- return;
- }
- // TODO: Do we need to lock the input method when the application reported an
- // incorrect package name?
- Slog.e(TAG, "Ignoring FixedImeApps due to the validation failure. uid=" + uid
- + " package=" + packageName);
+ // We have already made sure that the package name belongs to the application's UID.
+ // No further UID check is required.
+ if (SystemConfig.getInstance().getFixedImeApps().contains(mCurAttribute.packageName)) {
+ return;
}
}
@@ -2148,7 +2154,7 @@
// more quickly (not get stuck behind it initializing itself for the
// new focused input, even if its window wants to hide the IME).
boolean didStart = false;
-
+
switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
if (!isTextEditor || !doAutoShow) {
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index b36f515..56f9942 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -18,14 +18,18 @@
import android.Manifest;
import android.app.ActivityManager;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.service.persistentdata.IPersistentDataBlockService;
+import android.service.persistentdata.PersistentDataBlockManager;
import android.util.Slog;
import com.android.internal.R;
@@ -428,6 +432,29 @@
}
@Override
+ public void wipeIfAllowed(Bundle bundle, PendingIntent pi) {
+ // Should only be called by owner
+ if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) {
+ throw new SecurityException("Only the Owner is allowed to wipe");
+ }
+ // Caller must be able to query the the state of the PersistentDataBlock
+ enforcePersistentDataBlockAccess();
+ String allowedPackage = mContext.getResources()
+ .getString(R.string.config_persistentDataPackageName);
+ Intent intent = new Intent();
+ intent.setPackage(allowedPackage);
+ intent.setAction(PersistentDataBlockManager.ACTION_WIPE_IF_ALLOWED);
+ intent.putExtras(bundle);
+ intent.putExtra(PersistentDataBlockManager.EXTRA_WIPE_IF_ALLOWED_CALLBACK, pi);
+ long id = Binder.clearCallingIdentity();
+ try {
+ mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
+ } finally {
+ restoreCallingIdentity(id);
+ }
+ }
+
+ @Override
public void setOemUnlockEnabled(boolean enabled) {
// do not allow monkey to flip the flag
if (ActivityManager.isUserAMonkey()) {
@@ -450,10 +477,7 @@
@Override
public int getDataBlockSize() {
- if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
- != PackageManager.PERMISSION_GRANTED) {
- enforceUid(Binder.getCallingUid());
- }
+ enforcePersistentDataBlockAccess();
DataInputStream inputStream;
try {
@@ -475,6 +499,13 @@
}
}
+ private void enforcePersistentDataBlockAccess() {
+ if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
+ != PackageManager.PERMISSION_GRANTED) {
+ enforceUid(Binder.getCallingUid());
+ }
+ }
+
@Override
public long getMaximumDataBlockSize() {
long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index e92443c..3dece49 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1519,6 +1519,7 @@
} catch (DeadObjectException e) {
Slog.w(TAG, "Application dead when creating service " + r);
mAm.appDiedLocked(app);
+ throw e;
} finally {
if (!created) {
// Keep the executeNesting count accurate.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 607e09c..0581e22 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -100,6 +100,7 @@
import com.google.android.collect.Maps;
import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -986,6 +987,12 @@
private boolean mSleeping = false;
/**
+ * The process state used for processes that are running the top activities.
+ * This changes between TOP and TOP_SLEEPING to following mSleeping.
+ */
+ int mTopProcessState = ActivityManager.PROCESS_STATE_TOP;
+
+ /**
* Set while we are running a voice interaction. This overrides
* sleeping while it is active.
*/
@@ -2211,8 +2218,8 @@
mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"));
// User 0 is the first and only user that runs at boot.
- mStartedUsers.put(0, new UserStartedState(new UserHandle(0), true));
- mUserLru.add(Integer.valueOf(0));
+ mStartedUsers.put(UserHandle.USER_OWNER, new UserStartedState(UserHandle.OWNER, true));
+ mUserLru.add(UserHandle.USER_OWNER);
updateStartedUserArrayLocked();
GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
@@ -2466,6 +2473,13 @@
}
}
+ @Override
+ public void batterySendBroadcast(Intent intent) {
+ broadcastIntentLocked(null, null, intent, null,
+ null, 0, null, null, null, AppOpsManager.OP_NONE, false, false, -1,
+ Process.SYSTEM_UID, UserHandle.USER_ALL);
+ }
+
/**
* Initialize the application bind args. These are passed to each
* process when the bindApplication() IPC is sent to the process. They're
@@ -8641,7 +8655,7 @@
(ProviderInfo)providers.get(i);
boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo,
cpi.name, cpi.flags);
- if (singleton && UserHandle.getUserId(app.uid) != 0) {
+ if (singleton && UserHandle.getUserId(app.uid) != UserHandle.USER_OWNER) {
// This is a singleton provider, but a user besides the
// default user is asking to initialize a process it runs
// in... well, no, it doesn't actually run in this process,
@@ -9726,10 +9740,14 @@
void updateSleepIfNeededLocked() {
if (mSleeping && !shouldSleepLocked()) {
mSleeping = false;
+ mTopProcessState = ActivityManager.PROCESS_STATE_TOP;
mStackSupervisor.comeOutOfSleepIfNeededLocked();
+ updateOomAdjLocked();
} else if (!mSleeping && shouldSleepLocked()) {
mSleeping = true;
+ mTopProcessState = ActivityManager.PROCESS_STATE_TOP_SLEEPING;
mStackSupervisor.goingToSleepLocked();
+ updateOomAdjLocked();
// Initialize the wake times of all processes.
checkExcessivePowerUsageLocked(false);
@@ -10700,7 +10718,8 @@
for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
ProcessRecord proc = mLruProcesses.get(i);
if (proc.notCachedSinceIdle) {
- if (proc.setProcState > ActivityManager.PROCESS_STATE_TOP
+ if (proc.setProcState != ActivityManager.PROCESS_STATE_TOP
+ && proc.setProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
&& proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
if (doKilling && proc.initialIdlePss != 0
&& proc.lastPss > ((proc.initialIdlePss*3)/2)) {
@@ -12893,7 +12912,7 @@
StringBuilder sb = new StringBuilder();
sb.append(" ").append(proc).append('/');
UserHandle.formatUid(sb, uids.keyAt(j));
- Pair<Long, String> val = uids.valueAt(i);
+ Pair<Long, String> val = uids.valueAt(j);
sb.append(": "); DebugUtils.sizeValueToString(val.first, sb);
if (val.second != null) {
sb.append(", report to ").append(val.second);
@@ -13929,7 +13948,7 @@
} else if ("-h".equals(opt)) {
pw.println("meminfo dump options: [-a] [-d] [-c] [--oom] [process]");
pw.println(" -a: include all available information for each process.");
- pw.println(" -d: include dalvik details when dumping process details.");
+ pw.println(" -d: include dalvik details.");
pw.println(" -c: dump in a compact machine-parseable representation.");
pw.println(" --oom: only show processes organized by oom adj.");
pw.println(" --local: only collect details locally, don't call process.");
@@ -14016,6 +14035,8 @@
final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>();
long nativePss = 0;
long dalvikPss = 0;
+ long[] dalvikSubitemPss = dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+ EmptyArray.LONG;
long otherPss = 0;
long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
@@ -14093,6 +14114,9 @@
nativePss += mi.nativePss;
dalvikPss += mi.dalvikPss;
+ for (int j=0; j<dalvikSubitemPss.length; j++) {
+ dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ }
otherPss += mi.otherPss;
for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
long mem = mi.getOtherPss(j);
@@ -14151,6 +14175,10 @@
nativePss += mi.nativePss;
dalvikPss += mi.dalvikPss;
+ for (int j=0; j<dalvikSubitemPss.length; j++) {
+ dalvikSubitemPss[j] += mi.getOtherPss(
+ Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ }
otherPss += mi.otherPss;
for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
long mem = mi.getOtherPss(j);
@@ -14169,7 +14197,16 @@
ArrayList<MemItem> catMems = new ArrayList<MemItem>();
catMems.add(new MemItem("Native", "Native", nativePss, -1));
- catMems.add(new MemItem("Dalvik", "Dalvik", dalvikPss, -2));
+ final MemItem dalvikItem = new MemItem("Dalvik", "Dalvik", dalvikPss, -2);
+ if (dalvikSubitemPss.length > 0) {
+ dalvikItem.subitems = new ArrayList<MemItem>();
+ for (int j=0; j<dalvikSubitemPss.length; j++) {
+ final String name = Debug.MemoryInfo.getOtherLabel(
+ Debug.MemoryInfo.NUM_OTHER_STATS + j);
+ dalvikItem.subitems.add(new MemItem(name, name, dalvikSubitemPss[j], j));
+ }
+ }
+ catMems.add(dalvikItem);
catMems.add(new MemItem("Unknown", "Unknown", otherPss, -3));
for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
String label = Debug.MemoryInfo.getOtherLabel(j);
@@ -15461,8 +15498,9 @@
}
synchronized (this) {
- if (callerApp != null && callerApp.pid == 0) {
- // Caller already died
+ if (callerApp != null && (callerApp.thread == null
+ || callerApp.thread.asBinder() != caller.asBinder())) {
+ // Original caller already died
return null;
}
ReceiverList rl
@@ -15603,7 +15641,7 @@
}
List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
.queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, user);
- if (user != 0 && newReceivers != null) {
+ if (user != UserHandle.USER_OWNER && newReceivers != null) {
// If this is not the primary user, we need to check for
// any receivers that should be filtered out.
for (int i=0; i<newReceivers.size(); i++) {
@@ -16824,6 +16862,8 @@
app.systemNoUi = false;
+ final int PROCESS_STATE_TOP = mTopProcessState;
+
// Determine the importance of the process, starting with most
// important to least, and assign an appropriate OOM adjustment.
int adj;
@@ -16837,7 +16877,7 @@
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.adjType = "top-activity";
foregroundActivities = true;
- procState = ActivityManager.PROCESS_STATE_TOP;
+ procState = PROCESS_STATE_TOP;
} else if (app.instrumentationClass != null) {
// Don't want to kill running instrumentation.
adj = ProcessList.FOREGROUND_APP_ADJ;
@@ -16890,8 +16930,8 @@
adj = ProcessList.VISIBLE_APP_ADJ;
app.adjType = "visible";
}
- if (procState > ActivityManager.PROCESS_STATE_TOP) {
- procState = ActivityManager.PROCESS_STATE_TOP;
+ if (procState > PROCESS_STATE_TOP) {
+ procState = PROCESS_STATE_TOP;
}
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.cached = false;
@@ -16903,8 +16943,8 @@
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
app.adjType = "pausing";
}
- if (procState > ActivityManager.PROCESS_STATE_TOP) {
- procState = ActivityManager.PROCESS_STATE_TOP;
+ if (procState > PROCESS_STATE_TOP) {
+ procState = PROCESS_STATE_TOP;
}
schedGroup = Process.THREAD_GROUP_DEFAULT;
app.cached = false;
@@ -16943,7 +16983,7 @@
if (app.foregroundServices) {
// The user is aware of this app, so make it visible.
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
- procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
app.cached = false;
app.adjType = "fg-service";
schedGroup = Process.THREAD_GROUP_DEFAULT;
@@ -17472,7 +17512,7 @@
IApplicationThread thread = myProc.thread;
if (thread != null) {
try {
- if (true || DEBUG_PSS) Slog.d(TAG_PSS,
+ if (DEBUG_PSS) Slog.d(TAG_PSS,
"Requesting dump heap from "
+ myProc + " to " + heapdumpFile);
thread.dumpHeap(true, heapdumpFile.toString(), fd);
@@ -18757,7 +18797,7 @@
+ " does not match last path " + mMemWatchDumpFile);
return;
}
- if (true || DEBUG_PSS) Slog.d(TAG_PSS, "Dump heap finished for " + path);
+ if (DEBUG_PSS) Slog.d(TAG_PSS, "Dump heap finished for " + path);
mHandler.sendEmptyMessage(POST_DUMP_HEAP_NOTIFICATION_MSG);
}
}
@@ -19302,7 +19342,7 @@
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
- if (userId <= 0) {
+ if (userId < 0 || userId == UserHandle.USER_OWNER) {
throw new IllegalArgumentException("Can't stop primary user " + userId);
}
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
@@ -19548,14 +19588,6 @@
mUserSwitchObservers.unregister(observer);
}
- private boolean userExists(int userId) {
- if (userId == 0) {
- return true;
- }
- UserManagerService ums = getUserManagerLocked();
- return ums != null ? (ums.getUserInfo(userId) != null) : false;
- }
-
int[] getUsersLocked() {
UserManagerService ums = getUserManagerLocked();
return ums != null ? ums.getUserIds() : new int[] { 0 };
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index ddba1eb..2362d28 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1909,7 +1909,7 @@
next.sleeping = false;
mService.showAskCompatModeDialogLocked(next);
next.app.pendingUiClean = true;
- next.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
+ next.app.forceProcessStateUpTo(mService.mTopProcessState);
next.clearOptionsLocked();
next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
mService.isNextTransitionForward(), resumeAnimOptions);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d08cddc..c2f6bfd 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1229,7 +1229,7 @@
app.hasShownUi = true;
app.pendingUiClean = true;
}
- app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
+ app.forceProcessStateUpTo(mService.mTopProcessState);
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage,
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index ac70d88..ed108c2 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -260,6 +260,12 @@
}
}
+ public boolean isCharging() {
+ synchronized (mStats) {
+ return mStats.isCharging();
+ }
+ }
+
public long computeBatteryTimeRemaining() {
synchronized (mStats) {
long time = mStats.computeBatteryTimeRemaining(SystemClock.elapsedRealtime());
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 34c1c53..5b5ebef 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -50,7 +50,7 @@
* foreground priority, and one for normal (background-priority) broadcasts.
*/
public final class BroadcastQueue {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "BroadcastQueue" : TAG_AM;
+ private static final String TAG = "BroadcastQueue";
private static final String TAG_MU = TAG + POSTFIX_MU;
private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c7aa94c..cdfcd0c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -368,6 +368,12 @@
case ActivityManager.PROCESS_STATE_TOP:
procState = "T ";
break;
+ case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
+ procState = "FS";
+ break;
+ case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
+ procState = "TS";
+ break;
case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
procState = "IF";
break;
@@ -475,6 +481,8 @@
PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT
PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP
+ PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_BACKUP
@@ -492,6 +500,8 @@
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
@@ -509,6 +519,8 @@
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
@@ -526,6 +538,8 @@
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
@@ -543,6 +557,8 @@
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT
PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP
+ PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+ PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 191df2f..7866ddc 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -176,6 +176,7 @@
volatile private PowerManager.WakeLock mSyncManagerWakeLock;
volatile private boolean mDataConnectionIsConnected = false;
volatile private boolean mStorageIsLow = false;
+ volatile private boolean mDeviceIsIdle = false;
private final NotificationManager mNotificationMgr;
private AlarmManager mAlarmService = null;
@@ -221,6 +222,20 @@
}
};
+ private BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() {
+ @Override public void onReceive(Context context, Intent intent) {
+ boolean idle = mPowerManager.isDeviceIdleMode();
+ mDeviceIsIdle = idle;
+ if (idle) {
+ cancelActiveSync(
+ SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL,
+ null /* any sync */);
+ } else {
+ sendCheckAlarmsMessage();
+ }
+ }
+ };
+
private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -425,6 +440,9 @@
intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
context.registerReceiver(mStorageIntentReceiver, intentFilter);
+ intentFilter = new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+ context.registerReceiver(mDeviceIdleReceiver, intentFilter);
+
intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
intentFilter.setPriority(100);
context.registerReceiver(mShutdownIntentReceiver, intentFilter);
@@ -1312,6 +1330,7 @@
pw.println();
}
pw.print("memory low: "); pw.println(mStorageIsLow);
+ pw.print("device idle: "); pw.println(mDeviceIsIdle);
final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
@@ -2358,6 +2377,13 @@
return Long.MAX_VALUE;
}
+ if (mDeviceIsIdle) {
+ if (isLoggable) {
+ Log.v(TAG, "maybeStartNextSync: device idle, skipping");
+ }
+ return Long.MAX_VALUE;
+ }
+
// If the accounts aren't known yet then we aren't ready to run. We will be kicked
// when the account lookup request does complete.
if (mRunningAccounts == INITIAL_ACCOUNTS_ARRAY) {
@@ -2984,6 +3010,7 @@
// method to be called again
if (!mDataConnectionIsConnected) return;
if (mStorageIsLow) return;
+ if (mDeviceIsIdle) return;
// When the status bar notification should be raised
final long notificationTime =
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index c97bdb6..a3c9738 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -348,6 +348,10 @@
private int mActualBacklight = INITIAL_BACKLIGHT;
private boolean mChangeInProgress;
+ public PhotonicModulator() {
+ super("PhotonicModulator");
+ }
+
public boolean setState(int state, int backlight) {
synchronized (mLock) {
if (state != mPendingState || backlight != mPendingBacklight) {
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index 309e034..7c2aead 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -47,10 +47,6 @@
private static final Object sCreationLock = new Object();
private static volatile BatteryController sController;
- private static final String ACTION_CHARGING_STABLE =
- "com.android.server.task.controllers.BatteryController.ACTION_CHARGING_STABLE";
- /** Wait this long after phone is plugged in before doing any work. */
- private static final long STABLE_CHARGING_THRESHOLD_MILLIS = 2 * 60 * 1000; // 2 minutes.
private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
private ChargingTracker mChargeTracker;
@@ -91,9 +87,6 @@
taskStatus.chargingConstraintSatisfied.set(isOnStablePower);
}
}
- if (isOnStablePower) {
- mChargeTracker.setStableChargingAlarm();
- }
}
@Override
@@ -131,8 +124,6 @@
}
public class ChargingTracker extends BroadcastReceiver {
- private final AlarmManager mAlarm;
- private final PendingIntent mStableChargingTriggerIntent;
/**
* Track whether we're "charging", where charging means that we're ready to commit to
* doing work.
@@ -142,9 +133,6 @@
private boolean mBatteryHealthy;
public ChargingTracker() {
- mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- Intent intent = new Intent(ACTION_CHARGING_STABLE);
- mStableChargingTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
}
public void startTracking() {
@@ -154,10 +142,8 @@
filter.addAction(Intent.ACTION_BATTERY_LOW);
filter.addAction(Intent.ACTION_BATTERY_OKAY);
// Charging/not charging.
- filter.addAction(Intent.ACTION_POWER_CONNECTED);
- filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
- // Charging stable.
- filter.addAction(ACTION_CHARGING_STABLE);
+ filter.addAction(BatteryManager.ACTION_CHARGING);
+ filter.addAction(BatteryManager.ACTION_DISCHARGING);
mContext.registerReceiver(this, filter);
// Initialise tracker state.
@@ -195,45 +181,21 @@
}
mBatteryHealthy = true;
maybeReportNewChargingState();
- } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
+ } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
if (DEBUG) {
- Slog.d(TAG, "Received charging intent, setting alarm for "
- + STABLE_CHARGING_THRESHOLD_MILLIS);
+ Slog.d(TAG, "Received charging intent, fired @ "
+ + SystemClock.elapsedRealtime());
}
- // Set up an alarm for ACTION_CHARGING_STABLE - we don't want to kick off tasks
- // here if the user unplugs the phone immediately.
- setStableChargingAlarm();
mCharging = true;
- } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
+ maybeReportNewChargingState();
+ } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
if (DEBUG) {
- Slog.d(TAG, "Disconnected from power, cancelling any set alarms.");
+ Slog.d(TAG, "Disconnected from power.");
}
- // If an alarm is set, breathe a sigh of relief and cancel it - crisis averted.
- mAlarm.cancel(mStableChargingTriggerIntent);
mCharging = false;
maybeReportNewChargingState();
- }else if (ACTION_CHARGING_STABLE.equals(action)) {
- // Here's where we actually do the notify for a task being ready.
- if (DEBUG) {
- Slog.d(TAG, "Stable charging fired @ " + SystemClock.elapsedRealtime()
- + " charging: " + mCharging);
- }
- if (mCharging) { // Should never receive this intent if mCharging is false.
- maybeReportNewChargingState();
- }
}
}
-
- void setStableChargingAlarm() {
- final long alarmTriggerElapsed =
- SystemClock.elapsedRealtime() + STABLE_CHARGING_THRESHOLD_MILLIS;
- if (DEBUG) {
- Slog.d(TAG, "Setting stable alarm to go off in " +
- (STABLE_CHARGING_THRESHOLD_MILLIS / 1000) + "s");
- }
- mAlarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTriggerElapsed,
- mStableChargingTriggerIntent);
- }
}
@Override
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 9dcc529..2da9d8e 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -106,7 +106,8 @@
mMode = mode;
mOnMS = onMS;
mOffMS = offMS;
- Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", " + color + ")");
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", 0x"
+ + Integer.toHexString(color) + ")");
try {
setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);
} finally {
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index ab53fbc..fc2eced 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -25,12 +25,10 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionListener;
import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -41,50 +39,44 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Objects;
public class ConditionProviders extends ManagedServices {
- private static final Condition[] NO_CONDITIONS = new Condition[0];
+ private final ArrayList<ConditionRecord> mRecords = new ArrayList<>();
+ private final ArrayMap<IBinder, IConditionListener> mListeners = new ArrayMap<>();
+ private final ArraySet<String> mSystemConditionProviderNames;
+ private final ArraySet<SystemConditionProviderService> mSystemConditionProviders
+ = new ArraySet<>();
- private final ZenModeHelper mZenModeHelper;
- private final ArrayMap<IBinder, IConditionListener> mListeners
- = new ArrayMap<IBinder, IConditionListener>();
- private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
- private final ArraySet<String> mSystemConditionProviders;
- private final CountdownConditionProvider mCountdown;
- private final DowntimeConditionProvider mDowntime;
- private final NextAlarmConditionProvider mNextAlarm;
- private final NextAlarmTracker mNextAlarmTracker;
+ private Callback mCallback;
- private Condition mExitCondition;
- private ComponentName mExitConditionComponent;
-
- public ConditionProviders(Context context, Handler handler,
- UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
+ public ConditionProviders(Context context, Handler handler, UserProfiles userProfiles) {
super(context, handler, new Object(), userProfiles);
- mZenModeHelper = zenModeHelper;
- mZenModeHelper.addCallback(new ZenModeHelperCallback());
- mSystemConditionProviders = safeSet(PropConfig.getStringArray(mContext,
+ mSystemConditionProviderNames = safeSet(PropConfig.getStringArray(mContext,
"system.condition.providers",
R.array.config_system_condition_providers));
- final boolean countdown = mSystemConditionProviders.contains(ZenModeConfig.COUNTDOWN_PATH);
- final boolean downtime = mSystemConditionProviders.contains(ZenModeConfig.DOWNTIME_PATH);
- final boolean nextAlarm = mSystemConditionProviders.contains(ZenModeConfig.NEXT_ALARM_PATH);
- mNextAlarmTracker = (downtime || nextAlarm) ? new NextAlarmTracker(mContext) : null;
- mCountdown = countdown ? new CountdownConditionProvider() : null;
- mDowntime = downtime ? new DowntimeConditionProvider(this, mNextAlarmTracker,
- mZenModeHelper) : null;
- mNextAlarm = nextAlarm ? new NextAlarmConditionProvider(mNextAlarmTracker) : null;
- loadZenConfig();
}
- public boolean isSystemConditionProviderEnabled(String path) {
- return mSystemConditionProviders.contains(path);
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ public boolean isSystemProviderEnabled(String path) {
+ return mSystemConditionProviderNames.contains(path);
+ }
+
+ public void addSystemProvider(SystemConditionProviderService service) {
+ mSystemConditionProviders.add(service);
+ service.attachBase(mContext);
+ registerService(service.asInterface(), service.getComponent(), UserHandle.USER_OWNER);
+ }
+
+ public Iterable<SystemConditionProviderService> getSystemProviders() {
+ return mSystemConditionProviders;
}
@Override
protected Config getConfig() {
- Config c = new Config();
+ final Config c = new Config();
c.caption = "condition provider";
c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
@@ -98,12 +90,6 @@
public void dump(PrintWriter pw, DumpFilter filter) {
super.dump(pw, filter);
synchronized(mMutex) {
- if (filter == null) {
- pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):");
- for (int i = 0; i < mListeners.size(); i++) {
- pw.print(" "); pw.println(mListeners.keyAt(i));
- }
- }
pw.print(" mRecords("); pw.print(mRecords.size()); pw.println("):");
for (int i = 0; i < mRecords.size(); i++) {
final ConditionRecord r = mRecords.get(i);
@@ -115,18 +101,15 @@
}
}
}
- pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviders);
- if (mCountdown != null) {
- mCountdown.dump(pw, filter);
+ if (filter == null) {
+ pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):");
+ for (int i = 0; i < mListeners.size(); i++) {
+ pw.print(" "); pw.println(mListeners.keyAt(i));
+ }
}
- if (mDowntime != null) {
- mDowntime.dump(pw, filter);
- }
- if (mNextAlarm != null) {
- mNextAlarm.dump(pw, filter);
- }
- if (mNextAlarmTracker != null) {
- mNextAlarmTracker.dump(pw, filter);
+ pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviderNames);
+ for (int i = 0; i < mSystemConditionProviders.size(); i++) {
+ mSystemConditionProviders.valueAt(i).dump(pw, filter);
}
}
@@ -138,31 +121,16 @@
@Override
public void onBootPhaseAppsCanStart() {
super.onBootPhaseAppsCanStart();
- if (mNextAlarmTracker != null) {
- mNextAlarmTracker.init();
- }
- if (mCountdown != null) {
- mCountdown.attachBase(mContext);
- registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT,
- UserHandle.USER_OWNER);
- }
- if (mDowntime != null) {
- mDowntime.attachBase(mContext);
- registerService(mDowntime.asInterface(), DowntimeConditionProvider.COMPONENT,
- UserHandle.USER_OWNER);
- }
- if (mNextAlarm != null) {
- mNextAlarm.attachBase(mContext);
- registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT,
- UserHandle.USER_OWNER);
+ if (mCallback != null) {
+ mCallback.onBootComplete();
}
}
@Override
public void onUserSwitched() {
super.onUserSwitched();
- if (mNextAlarmTracker != null) {
- mNextAlarmTracker.onUserSwitched();
+ if (mCallback != null) {
+ mCallback.onUserSwitched();
}
}
@@ -174,24 +142,6 @@
} catch (RemoteException e) {
// we tried
}
- synchronized (mMutex) {
- if (info.component.equals(mExitConditionComponent)) {
- // ensure record exists, we'll wire it up and subscribe below
- final ConditionRecord manualRecord =
- getRecordLocked(mExitCondition.id, mExitConditionComponent);
- manualRecord.isManual = true;
- }
- final int N = mRecords.size();
- for(int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- if (!r.component.equals(info.component)) continue;
- r.info = info;
- // if automatic or manual, auto-subscribe
- if (r.isAutomatic || r.isManual) {
- subscribeLocked(r);
- }
- }
- }
}
@Override
@@ -200,15 +150,6 @@
for (int i = mRecords.size() - 1; i >= 0; i--) {
final ConditionRecord r = mRecords.get(i);
if (!r.component.equals(removed.component)) continue;
- if (r.isManual) {
- // removing the current manual condition, exit zen
- onManualConditionClearing();
- mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "manualServiceRemoved");
- }
- if (r.isAutomatic) {
- // removing an automatic condition, exit zen
- mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "automaticServiceRemoved");
- }
mRecords.remove(i);
}
}
@@ -219,9 +160,9 @@
}
}
- public void requestZenModeConditions(IConditionListener callback, int relevance) {
+ public void requestConditions(IConditionListener callback, int relevance) {
synchronized(mMutex) {
- if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
+ if (DEBUG) Slog.d(TAG, "requestConditions callback=" + callback
+ " relevance=" + Condition.relevanceToString(relevance));
if (callback == null) return;
relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
@@ -262,7 +203,8 @@
return rt;
}
- private ConditionRecord getRecordLocked(Uri id, ComponentName component) {
+ private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) {
+ if (id == null || component == null) return null;
final int N = mRecords.size();
for (int i = 0; i < N; i++) {
final ConditionRecord r = mRecords.get(i);
@@ -270,9 +212,12 @@
return r;
}
}
- final ConditionRecord r = new ConditionRecord(id, component);
- mRecords.add(r);
- return r;
+ if (create) {
+ final ConditionRecord r = new ConditionRecord(id, component);
+ mRecords.add(r);
+ return r;
+ }
+ return null;
}
public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
@@ -291,99 +236,48 @@
}
for (int i = 0; i < N; i++) {
final Condition c = conditions[i];
- final ConditionRecord r = getRecordLocked(c.id, info.component);
- final Condition oldCondition = r.condition;
- final boolean conditionUpdate = oldCondition != null && !oldCondition.equals(c);
+ final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/);
r.info = info;
r.condition = c;
- // if manual, exit zen if false (or failed), update if true (and changed)
- if (r.isManual) {
- if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
- final boolean failed = c.state == Condition.STATE_ERROR;
- if (failed) {
- Slog.w(TAG, "Exit zen: manual condition failed: " + c);
- } else if (DEBUG) {
- Slog.d(TAG, "Exit zen: manual condition false: " + c);
- }
- onManualConditionClearing();
- mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
- "manualConditionExit");
- unsubscribeLocked(r);
- r.isManual = false;
- } else if (c.state == Condition.STATE_TRUE && conditionUpdate) {
- if (DEBUG) Slog.d(TAG, "Current condition updated, still true. old="
- + oldCondition + " new=" + c);
- setZenModeCondition(c, "conditionUpdate");
- }
- }
- // if automatic, exit zen if false (or failed), enter zen if true
- if (r.isAutomatic) {
- if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
- final boolean failed = c.state == Condition.STATE_ERROR;
- if (failed) {
- Slog.w(TAG, "Exit zen: automatic condition failed: " + c);
- } else if (DEBUG) {
- Slog.d(TAG, "Exit zen: automatic condition false: " + c);
- }
- mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
- "automaticConditionExit");
- } else if (c.state == Condition.STATE_TRUE) {
- Slog.d(TAG, "Enter zen: automatic condition true: " + c);
- mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- "automaticConditionEnter");
- }
+ if (mCallback != null) {
+ mCallback.onConditionChanged(c.id, c);
}
}
}
}
- private void ensureRecordExists(Condition condition, IConditionProvider provider,
- ComponentName component) {
+ public void ensureRecordExists(ComponentName component, Uri conditionId,
+ IConditionProvider provider) {
// constructed by convention, make sure the record exists...
- final ConditionRecord r = getRecordLocked(condition.id, component);
+ final ConditionRecord r = getRecordLocked(conditionId, component, true /*create*/);
if (r.info == null) {
// ... and is associated with the in-process service
r.info = checkServiceTokenLocked(provider);
}
}
- public void setZenModeCondition(Condition condition, String reason) {
- if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition + " reason=" + reason);
- synchronized(mMutex) {
- ComponentName conditionComponent = null;
- if (condition != null) {
- if (mCountdown != null && ZenModeConfig.isValidCountdownConditionId(condition.id)) {
- ensureRecordExists(condition, mCountdown.asInterface(),
- CountdownConditionProvider.COMPONENT);
- }
- if (mDowntime != null && ZenModeConfig.isValidDowntimeConditionId(condition.id)) {
- ensureRecordExists(condition, mDowntime.asInterface(),
- DowntimeConditionProvider.COMPONENT);
- }
+ public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
+ synchronized (mMutex) {
+ final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
+ if (r == null) {
+ Slog.w(TAG, "Unable to subscribe to " + component + " " + conditionId);
+ return false;
}
- final int N = mRecords.size();
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- final boolean idEqual = condition != null && r.id.equals(condition.id);
- if (r.isManual && !idEqual) {
- // was previous manual condition, unsubscribe
- unsubscribeLocked(r);
- r.isManual = false;
- } else if (idEqual && !r.isManual) {
- // is new manual condition, subscribe
- subscribeLocked(r);
- r.isManual = true;
- }
- if (idEqual) {
- conditionComponent = r.component;
- }
+ if (r.subscribed) return true;
+ subscribeLocked(r);
+ return r.subscribed;
+ }
+ }
+
+ public void unsubscribeIfNecessary(ComponentName component, Uri conditionId) {
+ synchronized (mMutex) {
+ final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
+ if (r == null) {
+ Slog.w(TAG, "Unable to unsubscribe to " + component + " " + conditionId);
+ return;
}
- if (!Objects.equals(mExitCondition, condition)) {
- mExitCondition = condition;
- mExitConditionComponent = conditionComponent;
- ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, reason);
- saveZenConfigLocked();
- }
+ if (!r.subscribed) return;
+ unsubscribeLocked(r);;
}
}
@@ -393,8 +287,9 @@
RemoteException re = null;
if (provider != null) {
try {
- Slog.d(TAG, "Subscribing to " + r.id + " with " + provider);
+ Slog.d(TAG, "Subscribing to " + r.id + " with " + r.component);
provider.onSubscribe(r.id);
+ r.subscribed = true;
} catch (RemoteException e) {
Slog.w(TAG, "Error subscribing to " + r, e);
re = e;
@@ -417,53 +312,6 @@
return rt;
}
- public void setAutomaticZenModeConditions(Uri[] conditionIds) {
- setAutomaticZenModeConditions(conditionIds, true /*save*/);
- }
-
- private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) {
- if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions "
- + (conditionIds == null ? null : Arrays.asList(conditionIds)));
- synchronized(mMutex) {
- final ArraySet<Uri> newIds = safeSet(conditionIds);
- final int N = mRecords.size();
- boolean changed = false;
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- final boolean automatic = newIds.contains(r.id);
- if (!r.isAutomatic && automatic) {
- // subscribe to new automatic
- subscribeLocked(r);
- r.isAutomatic = true;
- changed = true;
- } else if (r.isAutomatic && !automatic) {
- // unsubscribe from old automatic
- unsubscribeLocked(r);
- r.isAutomatic = false;
- changed = true;
- }
- }
- if (save && changed) {
- saveZenConfigLocked();
- }
- }
- }
-
- public Condition[] getAutomaticZenModeConditions() {
- synchronized(mMutex) {
- final int N = mRecords.size();
- ArrayList<Condition> rt = null;
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = mRecords.get(i);
- if (r.isAutomatic && r.condition != null) {
- if (rt == null) rt = new ArrayList<Condition>();
- rt.add(r.condition);
- }
- }
- return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]);
- }
- }
-
private void unsubscribeLocked(ConditionRecord r) {
if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
final IConditionProvider provider = provider(r);
@@ -475,6 +323,7 @@
Slog.w(TAG, "Error unsubscribing to " + r, e);
re = e;
}
+ r.subscribed = false;
}
ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re);
}
@@ -495,7 +344,7 @@
for (int i = mRecords.size() - 1; i >= 0; i--) {
final ConditionRecord r = mRecords.get(i);
if (r.info != info) continue;
- if (r.isManual || r.isAutomatic) continue;
+ if (r.subscribed) continue;
mRecords.remove(i);
}
try {
@@ -506,103 +355,12 @@
}
}
- private void loadZenConfig() {
- final ZenModeConfig config = mZenModeHelper.getConfig();
- if (config == null) {
- if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
- return;
- }
- synchronized (mMutex) {
- final boolean changingExit = !Objects.equals(mExitCondition, config.exitCondition);
- mExitCondition = config.exitCondition;
- mExitConditionComponent = config.exitConditionComponent;
- if (changingExit) {
- ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "config");
- }
- if (mDowntime != null) {
- mDowntime.setConfig(config);
- }
- if (config.conditionComponents == null || config.conditionIds == null
- || config.conditionComponents.length != config.conditionIds.length) {
- if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
- setAutomaticZenModeConditions(null, false /*save*/);
- return;
- }
- final ArraySet<Uri> newIds = new ArraySet<Uri>();
- final int N = config.conditionComponents.length;
- for (int i = 0; i < N; i++) {
- final ComponentName component = config.conditionComponents[i];
- final Uri id = config.conditionIds[i];
- if (component != null && id != null) {
- getRecordLocked(id, component); // ensure record exists
- newIds.add(id);
- }
- }
- if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N);
- setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/);
- }
- }
-
- private void saveZenConfigLocked() {
- ZenModeConfig config = mZenModeHelper.getConfig();
- if (config == null) return;
- config = config.copy();
- final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>();
- final int automaticN = mRecords.size();
- for (int i = 0; i < automaticN; i++) {
- final ConditionRecord r = mRecords.get(i);
- if (r.isAutomatic) {
- automatic.add(r);
- }
- }
- if (automatic.isEmpty()) {
- config.conditionComponents = null;
- config.conditionIds = null;
- } else {
- final int N = automatic.size();
- config.conditionComponents = new ComponentName[N];
- config.conditionIds = new Uri[N];
- for (int i = 0; i < N; i++) {
- final ConditionRecord r = automatic.get(i);
- config.conditionComponents[i] = r.component;
- config.conditionIds[i] = r.id;
- }
- }
- config.exitCondition = mExitCondition;
- config.exitConditionComponent = mExitConditionComponent;
- if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
- mZenModeHelper.setConfig(config);
- }
-
- private void onManualConditionClearing() {
- if (mDowntime != null) {
- mDowntime.onManualConditionClearing();
- }
- }
-
- private class ZenModeHelperCallback extends ZenModeHelper.Callback {
- @Override
- void onConfigChanged() {
- loadZenConfig();
- }
-
- @Override
- void onZenModeChanged() {
- final int mode = mZenModeHelper.getZenMode();
- if (mode == Global.ZEN_MODE_OFF) {
- // ensure any manual condition is cleared
- setZenModeCondition(null, "zenOff");
- }
- }
- }
-
private static class ConditionRecord {
public final Uri id;
public final ComponentName component;
public Condition condition;
public ManagedServiceInfo info;
- public boolean isAutomatic;
- public boolean isManual;
+ public boolean subscribed;
private ConditionRecord(Uri id, ComponentName component) {
this.id = id;
@@ -612,10 +370,16 @@
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
- .append(id).append(",component=").append(component);
- if (isAutomatic) sb.append(",automatic");
- if (isManual) sb.append(",manual");
+ .append(id).append(",component=").append(component)
+ .append(",subscribed=").append(subscribed);
return sb.append(']').toString();
}
}
+
+ public interface Callback {
+ void onBootComplete();
+ void onConditionChanged(Uri id, Condition condition);
+ void onUserSwitched();
+ }
+
}
diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
index 37aacaa..d223353 100644
--- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java
+++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
@@ -25,7 +25,6 @@
import android.content.IntentFilter;
import android.net.Uri;
import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionProvider;
import android.service.notification.ZenModeConfig;
import android.text.format.DateUtils;
@@ -38,7 +37,7 @@
import java.util.Date;
/** Built-in zen condition provider for simple time-based conditions */
-public class CountdownConditionProvider extends ConditionProviderService {
+public class CountdownConditionProvider extends SystemConditionProviderService {
private static final String TAG = "CountdownConditions";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -59,6 +58,27 @@
if (DEBUG) Slog.d(TAG, "new CountdownConditionProvider()");
}
+ @Override
+ public ComponentName getComponent() {
+ return COMPONENT;
+ }
+
+ @Override
+ public boolean isValidConditionid(Uri id) {
+ return ZenModeConfig.isValidCountdownConditionId(id);
+ }
+
+ @Override
+ public void attachBase(Context base) {
+ attachBaseContext(base);
+ }
+
+ @Override
+ public IConditionProvider asInterface() {
+ return (IConditionProvider) onBind(null);
+ }
+
+ @Override
public void dump(PrintWriter pw, DumpFilter filter) {
pw.println(" CountdownConditionProvider:");
pw.print(" mConnected="); pw.println(mConnected);
@@ -154,11 +174,4 @@
return new Date(time) + " (" + time + ")";
}
- public void attachBase(Context base) {
- attachBaseContext(base);
- }
-
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
}
diff --git a/services/core/java/com/android/server/notification/DowntimeCalendar.java b/services/core/java/com/android/server/notification/DowntimeCalendar.java
deleted file mode 100644
index d14fd40..0000000
--- a/services/core/java/com/android/server/notification/DowntimeCalendar.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/**
- * Copyright (c) 2014, 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.server.notification;
-
-import java.util.Calendar;
-import java.util.Objects;
-import java.util.TimeZone;
-
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
-import android.util.ArraySet;
-
-public class DowntimeCalendar {
-
- private final ArraySet<Integer> mDays = new ArraySet<Integer>();
- private final Calendar mCalendar = Calendar.getInstance();
-
- private DowntimeInfo mInfo;
-
- @Override
- public String toString() {
- return "DowntimeCalendar[mDays=" + mDays + "]";
- }
-
- public void setDowntimeInfo(DowntimeInfo info) {
- if (Objects.equals(mInfo, info)) return;
- mInfo = info;
- updateDays();
- }
-
- public long nextDowntimeStart(long time) {
- if (mInfo == null || mDays.size() == 0) return Long.MAX_VALUE;
- final long start = getTime(time, mInfo.startHour, mInfo.startMinute);
- for (int i = 0; i < Calendar.SATURDAY; i++) {
- final long t = addDays(start, i);
- if (t > time && isInDowntime(t)) {
- return t;
- }
- }
- return Long.MAX_VALUE;
- }
-
- public void setTimeZone(TimeZone tz) {
- mCalendar.setTimeZone(tz);
- }
-
- public long getNextTime(long now, int hr, int min) {
- final long time = getTime(now, hr, min);
- return time <= now ? addDays(time, 1) : time;
- }
-
- private long getTime(long millis, int hour, int min) {
- mCalendar.setTimeInMillis(millis);
- mCalendar.set(Calendar.HOUR_OF_DAY, hour);
- mCalendar.set(Calendar.MINUTE, min);
- mCalendar.set(Calendar.SECOND, 0);
- mCalendar.set(Calendar.MILLISECOND, 0);
- return mCalendar.getTimeInMillis();
- }
-
- public boolean isInDowntime(long time) {
- if (mInfo == null || mDays.size() == 0) return false;
- final long start = getTime(time, mInfo.startHour, mInfo.startMinute);
- long end = getTime(time, mInfo.endHour, mInfo.endMinute);
- if (end <= start) {
- end = addDays(end, 1);
- }
- return isInDowntime(-1, time, start, end) || isInDowntime(0, time, start, end);
- }
-
- private boolean isInDowntime(int daysOffset, long time, long start, long end) {
- final int n = Calendar.SATURDAY;
- final int day = ((getDayOfWeek(time) - 1) + (daysOffset % n) + n) % n + 1;
- start = addDays(start, daysOffset);
- end = addDays(end, daysOffset);
- return mDays.contains(day) && time >= start && time < end;
- }
-
- private int getDayOfWeek(long time) {
- mCalendar.setTimeInMillis(time);
- return mCalendar.get(Calendar.DAY_OF_WEEK);
- }
-
- private void updateDays() {
- mDays.clear();
- if (mInfo != null) {
- final int[] days = ZenModeConfig.tryParseDays(mInfo.mode);
- for (int i = 0; days != null && i < days.length; i++) {
- mDays.add(days[i]);
- }
- }
- }
-
- private long addDays(long time, int days) {
- mCalendar.setTimeInMillis(time);
- mCalendar.add(Calendar.DATE, days);
- return mCalendar.getTimeInMillis();
- }
-}
diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
deleted file mode 100644
index df4ecfd..0000000
--- a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/**
- * Copyright (c) 2014, 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.server.notification;
-
-import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.Uri;
-import android.provider.Settings.Global;
-import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
-import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
-import android.text.format.DateFormat;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.internal.R;
-import com.android.server.notification.NotificationManagerService.DumpFilter;
-
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.TimeZone;
-
-/** Built-in zen condition provider for managing downtime */
-public class DowntimeConditionProvider extends ConditionProviderService {
- private static final String TAG = "DowntimeConditions";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- public static final ComponentName COMPONENT =
- new ComponentName("android", DowntimeConditionProvider.class.getName());
-
- private static final String ENTER_ACTION = TAG + ".enter";
- private static final int ENTER_CODE = 100;
- private static final String EXIT_ACTION = TAG + ".exit";
- private static final int EXIT_CODE = 101;
- private static final String EXTRA_TIME = "time";
-
- private static final long SECONDS = 1000;
- private static final long MINUTES = 60 * SECONDS;
- private static final long HOURS = 60 * MINUTES;
-
- private final Context mContext = this;
- private final DowntimeCalendar mCalendar = new DowntimeCalendar();
- private final FiredAlarms mFiredAlarms = new FiredAlarms();
- private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
- private final ConditionProviders mConditionProviders;
- private final NextAlarmTracker mTracker;
- private final ZenModeHelper mZenModeHelper;
-
- private boolean mConnected;
- private long mLookaheadThreshold;
- private ZenModeConfig mConfig;
- private boolean mDowntimed;
- private boolean mConditionClearing;
- private boolean mRequesting;
-
- public DowntimeConditionProvider(ConditionProviders conditionProviders,
- NextAlarmTracker tracker, ZenModeHelper zenModeHelper) {
- if (DEBUG) Slog.d(TAG, "new DowntimeConditionProvider()");
- mConditionProviders = conditionProviders;
- mTracker = tracker;
- mZenModeHelper = zenModeHelper;
- }
-
- public void dump(PrintWriter pw, DumpFilter filter) {
- pw.println(" DowntimeConditionProvider:");
- pw.print(" mConnected="); pw.println(mConnected);
- pw.print(" mSubscriptions="); pw.println(mSubscriptions);
- pw.print(" mLookaheadThreshold="); pw.print(mLookaheadThreshold);
- pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")");
- pw.print(" mCalendar="); pw.println(mCalendar);
- pw.print(" mFiredAlarms="); pw.println(mFiredAlarms);
- pw.print(" mDowntimed="); pw.println(mDowntimed);
- pw.print(" mConditionClearing="); pw.println(mConditionClearing);
- pw.print(" mRequesting="); pw.println(mRequesting);
- }
-
- public void attachBase(Context base) {
- attachBaseContext(base);
- }
-
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
-
- @Override
- public void onConnected() {
- if (DEBUG) Slog.d(TAG, "onConnected");
- mConnected = true;
- mLookaheadThreshold = PropConfig.getInt(mContext, "downtime.condition.lookahead",
- R.integer.config_downtime_condition_lookahead_threshold_hrs) * HOURS;
- final IntentFilter filter = new IntentFilter();
- filter.addAction(ENTER_ACTION);
- filter.addAction(EXIT_ACTION);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- mContext.registerReceiver(mReceiver, filter);
- mTracker.addCallback(mTrackerCallback);
- mZenModeHelper.addCallback(mZenCallback);
- init();
- }
-
- @Override
- public void onDestroy() {
- if (DEBUG) Slog.d(TAG, "onDestroy");
- mTracker.removeCallback(mTrackerCallback);
- mZenModeHelper.removeCallback(mZenCallback);
- mConnected = false;
- }
-
- @Override
- public void onRequestConditions(int relevance) {
- if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
- if (!mConnected) return;
- mRequesting = (relevance & Condition.FLAG_RELEVANT_NOW) != 0;
- evaluateSubscriptions();
- }
-
- @Override
- public void onSubscribe(Uri conditionId) {
- if (DEBUG) Slog.d(TAG, "onSubscribe conditionId=" + conditionId);
- final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId);
- if (downtime == null) return;
- mFiredAlarms.clear();
- mSubscriptions.add(conditionId);
- notifyCondition(downtime);
- }
-
- private boolean shouldShowCondition() {
- final long now = System.currentTimeMillis();
- if (DEBUG) Slog.d(TAG, "shouldShowCondition now=" + mCalendar.isInDowntime(now)
- + " lookahead="
- + (mCalendar.nextDowntimeStart(now) <= (now + mLookaheadThreshold)));
- return mCalendar.isInDowntime(now)
- || mCalendar.nextDowntimeStart(now) <= (now + mLookaheadThreshold);
- }
-
- private void notifyCondition(DowntimeInfo downtime) {
- if (mConfig == null) {
- // we don't know yet
- notifyCondition(createCondition(downtime, Condition.STATE_UNKNOWN));
- return;
- }
- if (!downtime.equals(mConfig.toDowntimeInfo())) {
- // not the configured downtime, consider it false
- notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
- return;
- }
- if (!shouldShowCondition()) {
- // configured downtime, but not within the time range
- notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
- return;
- }
- if (isZenNone() && mFiredAlarms.findBefore(System.currentTimeMillis())) {
- // within the configured time range, but wake up if none and the next alarm is fired
- notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
- return;
- }
- // within the configured time range, condition still valid
- notifyCondition(createCondition(downtime, Condition.STATE_TRUE));
- }
-
- private boolean isZenNone() {
- return mZenModeHelper.getZenMode() == Global.ZEN_MODE_NO_INTERRUPTIONS;
- }
-
- private boolean isZenOff() {
- return mZenModeHelper.getZenMode() == Global.ZEN_MODE_OFF;
- }
-
- private void evaluateSubscriptions() {
- ArraySet<Uri> conditions = mSubscriptions;
- if (mConfig != null && mRequesting && shouldShowCondition()) {
- final Uri id = ZenModeConfig.toDowntimeConditionId(mConfig.toDowntimeInfo());
- if (!conditions.contains(id)) {
- conditions = new ArraySet<Uri>(conditions);
- conditions.add(id);
- }
- }
- for (Uri conditionId : conditions) {
- final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId);
- if (downtime != null) {
- notifyCondition(downtime);
- }
- }
- }
-
- @Override
- public void onUnsubscribe(Uri conditionId) {
- final boolean current = mSubscriptions.contains(conditionId);
- if (DEBUG) Slog.d(TAG, "onUnsubscribe conditionId=" + conditionId + " current=" + current);
- mSubscriptions.remove(conditionId);
- mFiredAlarms.clear();
- }
-
- public void setConfig(ZenModeConfig config) {
- if (Objects.equals(mConfig, config)) return;
- final boolean downtimeChanged = mConfig == null || config == null
- || !mConfig.toDowntimeInfo().equals(config.toDowntimeInfo());
- mConfig = config;
- if (DEBUG) Slog.d(TAG, "setConfig downtimeChanged=" + downtimeChanged);
- if (mConnected && downtimeChanged) {
- mDowntimed = false;
- init();
- }
- // when active, mark downtime as entered for today
- if (mConfig != null && mConfig.exitCondition != null
- && ZenModeConfig.isValidDowntimeConditionId(mConfig.exitCondition.id)) {
- mDowntimed = true;
- }
- }
-
- public void onManualConditionClearing() {
- mConditionClearing = true;
- }
-
- private Condition createCondition(DowntimeInfo downtime, int state) {
- if (downtime == null) return null;
- final Uri id = ZenModeConfig.toDowntimeConditionId(downtime);
- final String skeleton = DateFormat.is24HourFormat(mContext) ? "Hm" : "hma";
- final Locale locale = Locale.getDefault();
- final String pattern = DateFormat.getBestDateTimePattern(locale, skeleton);
- final long now = System.currentTimeMillis();
- long endTime = mCalendar.getNextTime(now, downtime.endHour, downtime.endMinute);
- if (isZenNone()) {
- final AlarmClockInfo nextAlarm = mTracker.getNextAlarm();
- final long nextAlarmTime = nextAlarm != null ? nextAlarm.getTriggerTime() : 0;
- if (nextAlarmTime > now && nextAlarmTime < endTime) {
- endTime = nextAlarmTime;
- }
- }
- final String formatted = new SimpleDateFormat(pattern, locale).format(new Date(endTime));
- final String summary = mContext.getString(R.string.downtime_condition_summary, formatted);
- final String line1 = mContext.getString(R.string.downtime_condition_line_one);
- return new Condition(id, summary, line1, formatted, 0, state, Condition.FLAG_RELEVANT_NOW);
- }
-
- private void init() {
- mCalendar.setDowntimeInfo(mConfig != null ? mConfig.toDowntimeInfo() : null);
- evaluateSubscriptions();
- updateAlarms();
- evaluateAutotrigger();
- }
-
- private void updateAlarms() {
- if (mConfig == null) return;
- updateAlarm(ENTER_ACTION, ENTER_CODE, mConfig.sleepStartHour, mConfig.sleepStartMinute);
- updateAlarm(EXIT_ACTION, EXIT_CODE, mConfig.sleepEndHour, mConfig.sleepEndMinute);
- }
-
-
- private void updateAlarm(String action, int requestCode, int hr, int min) {
- final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- final long now = System.currentTimeMillis();
- final long time = mCalendar.getNextTime(now, hr, min);
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode,
- new Intent(action)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_TIME, time),
- PendingIntent.FLAG_UPDATE_CURRENT);
- alarms.cancel(pendingIntent);
- if (mConfig.sleepMode != null) {
- if (DEBUG) Slog.d(TAG, String.format("Scheduling %s for %s, in %s, now=%s",
- action, ts(time), NextAlarmTracker.formatDuration(time - now), ts(now)));
- alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
- }
- }
-
- private static String ts(long time) {
- return new Date(time) + " (" + time + ")";
- }
-
- private void onEvaluateNextAlarm(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- if (!booted) return; // we don't know yet
- if (DEBUG) Slog.d(TAG, "onEvaluateNextAlarm " + mTracker.formatAlarmDebug(nextAlarm));
- if (nextAlarm != null && wakeupTime > 0 && System.currentTimeMillis() > wakeupTime) {
- if (DEBUG) Slog.d(TAG, "Alarm fired: " + mTracker.formatAlarmDebug(wakeupTime));
- mFiredAlarms.add(wakeupTime);
- }
- evaluateSubscriptions();
- }
-
- private void evaluateAutotrigger() {
- String skipReason = null;
- if (mConfig == null) {
- skipReason = "no config";
- } else if (mDowntimed) {
- skipReason = "already downtimed";
- } else if (mZenModeHelper.getZenMode() != Global.ZEN_MODE_OFF) {
- skipReason = "already in zen";
- } else if (!mCalendar.isInDowntime(System.currentTimeMillis())) {
- skipReason = "not in downtime";
- }
- if (skipReason != null) {
- ZenLog.traceDowntimeAutotrigger("Autotrigger skipped: " + skipReason);
- return;
- }
- ZenLog.traceDowntimeAutotrigger("Autotrigger fired");
- mZenModeHelper.setZenMode(mConfig.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS
- : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, "downtime");
- final Condition condition = createCondition(mConfig.toDowntimeInfo(), Condition.STATE_TRUE);
- mConditionProviders.setZenModeCondition(condition, "downtime");
- }
-
- private BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final long now = System.currentTimeMillis();
- if (ENTER_ACTION.equals(action) || EXIT_ACTION.equals(action)) {
- final long schTime = intent.getLongExtra(EXTRA_TIME, 0);
- if (DEBUG) Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s",
- action, ts(schTime), ts(now), now - schTime));
- if (ENTER_ACTION.equals(action)) {
- evaluateAutotrigger();
- } else /*EXIT_ACTION*/ {
- mDowntimed = false;
- }
- mFiredAlarms.clear();
- } else if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
- if (DEBUG) Slog.d(TAG, "timezone changed to " + TimeZone.getDefault());
- mCalendar.setTimeZone(TimeZone.getDefault());
- mFiredAlarms.clear();
- } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
- if (DEBUG) Slog.d(TAG, "time changed to " + now);
- mFiredAlarms.clear();
- } else {
- if (DEBUG) Slog.d(TAG, action + " fired at " + now);
- }
- evaluateSubscriptions();
- updateAlarms();
- }
- };
-
- private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() {
- @Override
- public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- DowntimeConditionProvider.this.onEvaluateNextAlarm(nextAlarm, wakeupTime, booted);
- }
- };
-
- private final ZenModeHelper.Callback mZenCallback = new ZenModeHelper.Callback() {
- @Override
- void onZenModeChanged() {
- if (mConditionClearing && isZenOff()) {
- evaluateAutotrigger();
- }
- mConditionClearing = false;
- evaluateSubscriptions();
- }
- };
-
- private class FiredAlarms {
- private final ArraySet<Long> mFiredAlarms = new ArraySet<Long>();
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mFiredAlarms.size(); i++) {
- if (i > 0) sb.append(',');
- sb.append(mTracker.formatAlarmDebug(mFiredAlarms.valueAt(i)));
- }
- return sb.toString();
- }
-
- public void add(long firedAlarm) {
- mFiredAlarms.add(firedAlarm);
- }
-
- public void clear() {
- mFiredAlarms.clear();
- }
-
- public boolean findBefore(long time) {
- for (int i = 0; i < mFiredAlarms.size(); i++) {
- if (mFiredAlarms.valueAt(i) < time) {
- return true;
- }
- }
- return false;
- }
- }
-}
diff --git a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java b/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
deleted file mode 100644
index 1634c65..0000000
--- a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2014 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.server.notification;
-
-import android.app.AlarmManager;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.Uri;
-import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
-import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.internal.R;
-import com.android.server.notification.NotificationManagerService.DumpFilter;
-
-import java.io.PrintWriter;
-
-/**
- * Built-in zen condition provider for alarm-clock-based conditions.
- *
- * <p>If the user's next alarm is within a lookahead threshold (config, default 12hrs), advertise
- * it as an exit condition for zen mode.
- *
- * <p>The next alarm is defined as {@link AlarmManager#getNextAlarmClock(int)}, which does not
- * survive a reboot. Maintain the illusion of a consistent next alarm value by holding on to
- * a persisted condition until we receive the first value after reboot, or timeout with no value.
- */
-public class NextAlarmConditionProvider extends ConditionProviderService {
- private static final String TAG = "NextAlarmConditions";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final long SECONDS = 1000;
- private static final long MINUTES = 60 * SECONDS;
- private static final long HOURS = 60 * MINUTES;
-
- private static final long BAD_CONDITION = -1;
-
- public static final ComponentName COMPONENT =
- new ComponentName("android", NextAlarmConditionProvider.class.getName());
-
- private final Context mContext = this;
- private final NextAlarmTracker mTracker;
- private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
-
- private boolean mConnected;
- private long mLookaheadThreshold;
- private boolean mRequesting;
-
- public NextAlarmConditionProvider(NextAlarmTracker tracker) {
- if (DEBUG) Slog.d(TAG, "new NextAlarmConditionProvider()");
- mTracker = tracker;
- }
-
- public void dump(PrintWriter pw, DumpFilter filter) {
- pw.println(" NextAlarmConditionProvider:");
- pw.print(" mConnected="); pw.println(mConnected);
- pw.print(" mLookaheadThreshold="); pw.print(mLookaheadThreshold);
- pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")");
- pw.print(" mSubscriptions="); pw.println(mSubscriptions);
- pw.print(" mRequesting="); pw.println(mRequesting);
- }
-
- @Override
- public void onConnected() {
- if (DEBUG) Slog.d(TAG, "onConnected");
- mLookaheadThreshold = PropConfig.getInt(mContext, "nextalarm.condition.lookahead",
- R.integer.config_next_alarm_condition_lookahead_threshold_hrs) * HOURS;
- mConnected = true;
- mTracker.addCallback(mTrackerCallback);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (DEBUG) Slog.d(TAG, "onDestroy");
- mTracker.removeCallback(mTrackerCallback);
- mConnected = false;
- }
-
- @Override
- public void onRequestConditions(int relevance) {
- if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
- if (!mConnected) return;
- mRequesting = (relevance & Condition.FLAG_RELEVANT_NOW) != 0;
- mTracker.evaluate();
- }
-
- @Override
- public void onSubscribe(Uri conditionId) {
- if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
- if (tryParseNextAlarmCondition(conditionId) == BAD_CONDITION) {
- notifyCondition(conditionId, null, Condition.STATE_FALSE, "badCondition");
- return;
- }
- mSubscriptions.add(conditionId);
- mTracker.evaluate();
- }
-
- @Override
- public void onUnsubscribe(Uri conditionId) {
- if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
- mSubscriptions.remove(conditionId);
- }
-
- public void attachBase(Context base) {
- attachBaseContext(base);
- }
-
- public IConditionProvider asInterface() {
- return (IConditionProvider) onBind(null);
- }
-
- private boolean isWithinLookaheadThreshold(AlarmClockInfo alarm) {
- if (alarm == null) return false;
- final long delta = NextAlarmTracker.getEarlyTriggerTime(alarm) - System.currentTimeMillis();
- return delta > 0 && (mLookaheadThreshold <= 0 || delta < mLookaheadThreshold);
- }
-
- private void notifyCondition(Uri id, AlarmClockInfo alarm, int state, String reason) {
- final String formattedAlarm = alarm == null ? "" : mTracker.formatAlarm(alarm);
- if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state)
- + " alarm=" + formattedAlarm + " reason=" + reason);
- notifyCondition(new Condition(id,
- mContext.getString(R.string.zen_mode_next_alarm_summary, formattedAlarm),
- mContext.getString(R.string.zen_mode_next_alarm_line_one),
- formattedAlarm, 0, state, Condition.FLAG_RELEVANT_NOW));
- }
-
- private Uri newConditionId(AlarmClockInfo nextAlarm) {
- return new Uri.Builder().scheme(Condition.SCHEME)
- .authority(ZenModeConfig.SYSTEM_AUTHORITY)
- .appendPath(ZenModeConfig.NEXT_ALARM_PATH)
- .appendPath(Integer.toString(mTracker.getCurrentUserId()))
- .appendPath(Long.toString(nextAlarm.getTriggerTime()))
- .build();
- }
-
- private long tryParseNextAlarmCondition(Uri conditionId) {
- return conditionId != null && conditionId.getScheme().equals(Condition.SCHEME)
- && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
- && conditionId.getPathSegments().size() == 3
- && conditionId.getPathSegments().get(0).equals(ZenModeConfig.NEXT_ALARM_PATH)
- && conditionId.getPathSegments().get(1)
- .equals(Integer.toString(mTracker.getCurrentUserId()))
- ? tryParseLong(conditionId.getPathSegments().get(2), BAD_CONDITION)
- : BAD_CONDITION;
- }
-
- private static long tryParseLong(String value, long defValue) {
- if (TextUtils.isEmpty(value)) return defValue;
- try {
- return Long.valueOf(value);
- } catch (NumberFormatException e) {
- return defValue;
- }
- }
-
- private void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- final boolean withinThreshold = isWithinLookaheadThreshold(nextAlarm);
- final long nextAlarmTime = nextAlarm != null ? nextAlarm.getTriggerTime() : 0;
- if (DEBUG) Slog.d(TAG, "onEvaluate mSubscriptions=" + mSubscriptions
- + " nextAlarmTime=" + mTracker.formatAlarmDebug(nextAlarmTime)
- + " nextAlarmWakeup=" + mTracker.formatAlarmDebug(wakeupTime)
- + " withinThreshold=" + withinThreshold
- + " booted=" + booted);
-
- ArraySet<Uri> conditions = mSubscriptions;
- if (mRequesting && nextAlarm != null && withinThreshold) {
- final Uri id = newConditionId(nextAlarm);
- if (!conditions.contains(id)) {
- conditions = new ArraySet<Uri>(conditions);
- conditions.add(id);
- }
- }
- for (Uri conditionId : conditions) {
- final long time = tryParseNextAlarmCondition(conditionId);
- if (time == BAD_CONDITION) {
- notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "badCondition");
- } else if (!booted) {
- // we don't know yet
- if (mSubscriptions.contains(conditionId)) {
- notifyCondition(conditionId, nextAlarm, Condition.STATE_UNKNOWN, "!booted");
- }
- } else if (time != nextAlarmTime) {
- // next alarm changed since subscription, consider obsolete
- notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "changed");
- } else if (!withinThreshold) {
- // next alarm outside threshold or in the past, condition = false
- notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "!within");
- } else {
- // next alarm within threshold and in the future, condition = true
- notifyCondition(conditionId, nextAlarm, Condition.STATE_TRUE, "within");
- }
- }
- }
-
- private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() {
- @Override
- public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- NextAlarmConditionProvider.this.onEvaluate(nextAlarm, wakeupTime, booted);
- }
- };
-}
diff --git a/services/core/java/com/android/server/notification/NextAlarmTracker.java b/services/core/java/com/android/server/notification/NextAlarmTracker.java
deleted file mode 100644
index 234f545..0000000
--- a/services/core/java/com/android/server/notification/NextAlarmTracker.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright (C) 2014 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.server.notification;
-
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.text.format.DateFormat;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.server.notification.NotificationManagerService.DumpFilter;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Locale;
-
-/** Helper for tracking updates to the current user's next alarm. */
-public class NextAlarmTracker {
- private static final String TAG = "NextAlarmTracker";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final String ACTION_TRIGGER = TAG + ".trigger";
- private static final String EXTRA_TRIGGER = "trigger";
- private static final int REQUEST_CODE = 100;
-
- private static final long SECONDS = 1000;
- private static final long MINUTES = 60 * SECONDS;
- private static final long NEXT_ALARM_UPDATE_DELAY = 1 * SECONDS; // treat clear+set as update
- private static final long EARLY = 5 * SECONDS; // fire early, ensure alarm stream is unmuted
- private static final long WAIT_AFTER_INIT = 5 * MINUTES;// for initial alarm re-registration
- private static final long WAIT_AFTER_BOOT = 20 * SECONDS; // for initial alarm re-registration
-
- private final Context mContext;
- private final H mHandler = new H();
- private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
-
- private long mInit;
- private boolean mRegistered;
- private AlarmManager mAlarmManager;
- private int mCurrentUserId;
- private long mScheduledAlarmTime;
- private long mBootCompleted;
- private PowerManager.WakeLock mWakeLock;
-
- public NextAlarmTracker(Context context) {
- mContext = context;
- }
-
- public void dump(PrintWriter pw, DumpFilter filter) {
- pw.println(" NextAlarmTracker:");
- pw.print(" len(mCallbacks)="); pw.println(mCallbacks.size());
- pw.print(" mRegistered="); pw.println(mRegistered);
- pw.print(" mInit="); pw.println(mInit);
- pw.print(" mBootCompleted="); pw.println(mBootCompleted);
- pw.print(" mCurrentUserId="); pw.println(mCurrentUserId);
- pw.print(" mScheduledAlarmTime="); pw.println(formatAlarmDebug(mScheduledAlarmTime));
- pw.print(" mWakeLock="); pw.println(mWakeLock);
- }
-
- public void addCallback(Callback callback) {
- mCallbacks.add(callback);
- }
-
- public void removeCallback(Callback callback) {
- mCallbacks.remove(callback);
- }
-
- public int getCurrentUserId() {
- return mCurrentUserId;
- }
-
- public AlarmClockInfo getNextAlarm() {
- return mAlarmManager.getNextAlarmClock(mCurrentUserId);
- }
-
- public void onUserSwitched() {
- reset();
- }
-
- public void init() {
- mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- final PowerManager p = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = p.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
- mInit = System.currentTimeMillis();
- reset();
- }
-
- public void reset() {
- if (mRegistered) {
- mContext.unregisterReceiver(mReceiver);
- }
- mCurrentUserId = ActivityManager.getCurrentUser();
- final IntentFilter filter = new IntentFilter();
- filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
- filter.addAction(ACTION_TRIGGER);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- filter.addAction(Intent.ACTION_BOOT_COMPLETED);
- mContext.registerReceiverAsUser(mReceiver, new UserHandle(mCurrentUserId), filter, null,
- null);
- mRegistered = true;
- evaluate();
- }
-
- public void destroy() {
- if (mRegistered) {
- mContext.unregisterReceiver(mReceiver);
- mRegistered = false;
- }
- }
-
- public void evaluate() {
- mHandler.postEvaluate(0);
- }
-
- private void fireEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
- for (Callback callback : mCallbacks) {
- callback.onEvaluate(nextAlarm, wakeupTime, booted);
- }
- }
-
- private void handleEvaluate() {
- final AlarmClockInfo nextAlarm = mAlarmManager.getNextAlarmClock(mCurrentUserId);
- final long triggerTime = getEarlyTriggerTime(nextAlarm);
- final long now = System.currentTimeMillis();
- final boolean alarmUpcoming = triggerTime > now;
- final boolean booted = isDoneWaitingAfterBoot(now);
- if (DEBUG) Slog.d(TAG, "handleEvaluate nextAlarm=" + formatAlarmDebug(triggerTime)
- + " alarmUpcoming=" + alarmUpcoming
- + " booted=" + booted);
- fireEvaluate(nextAlarm, triggerTime, booted);
- if (!booted) {
- // recheck after boot
- final long recheckTime = (mBootCompleted > 0 ? mBootCompleted : now) + WAIT_AFTER_BOOT;
- rescheduleAlarm(recheckTime);
- return;
- }
- if (alarmUpcoming) {
- // wake up just before the next alarm
- rescheduleAlarm(triggerTime);
- }
- }
-
- public static long getEarlyTriggerTime(AlarmClockInfo alarm) {
- return alarm != null ? (alarm.getTriggerTime() - EARLY) : 0;
- }
-
- private boolean isDoneWaitingAfterBoot(long time) {
- if (mBootCompleted > 0) return (time - mBootCompleted) > WAIT_AFTER_BOOT;
- if (mInit > 0) return (time - mInit) > WAIT_AFTER_INIT;
- return true;
- }
-
- public static String formatDuration(long millis) {
- final StringBuilder sb = new StringBuilder();
- TimeUtils.formatDuration(millis, sb);
- return sb.toString();
- }
-
- public String formatAlarm(AlarmClockInfo alarm) {
- return alarm != null ? formatAlarm(alarm.getTriggerTime()) : null;
- }
-
- private String formatAlarm(long time) {
- return formatAlarm(time, "Hm", "hma");
- }
-
- private String formatAlarm(long time, String skeleton24, String skeleton12) {
- final String skeleton = DateFormat.is24HourFormat(mContext) ? skeleton24 : skeleton12;
- final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
- return DateFormat.format(pattern, time).toString();
- }
-
- public String formatAlarmDebug(AlarmClockInfo alarm) {
- return formatAlarmDebug(alarm != null ? alarm.getTriggerTime() : 0);
- }
-
- public String formatAlarmDebug(long time) {
- if (time <= 0) return Long.toString(time);
- return String.format("%s (%s)", time, formatAlarm(time, "Hms", "hmsa"));
- }
-
- private void rescheduleAlarm(long time) {
- if (DEBUG) Slog.d(TAG, "rescheduleAlarm " + time);
- final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE,
- new Intent(ACTION_TRIGGER)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_TRIGGER, time),
- PendingIntent.FLAG_UPDATE_CURRENT);
- alarms.cancel(pendingIntent);
- mScheduledAlarmTime = time;
- if (time > 0) {
- if (DEBUG) Slog.d(TAG, String.format("Scheduling alarm for %s (in %s)",
- formatAlarmDebug(time), formatDuration(time - System.currentTimeMillis())));
- alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
- }
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (DEBUG) Slog.d(TAG, "onReceive " + action);
- long delay = 0;
- if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
- delay = NEXT_ALARM_UPDATE_DELAY;
- if (DEBUG) Slog.d(TAG, String.format(" next alarm for user %s: %s",
- mCurrentUserId,
- formatAlarmDebug(mAlarmManager.getNextAlarmClock(mCurrentUserId))));
- } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
- mBootCompleted = System.currentTimeMillis();
- }
- mHandler.postEvaluate(delay);
- mWakeLock.acquire(delay + 5000); // stay awake during evaluate
- }
- };
-
- private class H extends Handler {
- private static final int MSG_EVALUATE = 1;
-
- public void postEvaluate(long delay) {
- removeMessages(MSG_EVALUATE);
- sendEmptyMessageDelayed(MSG_EVALUATE, delay);
- }
-
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_EVALUATE) {
- handleEvaluate();
- }
- }
- }
-
- public interface Callback {
- void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted);
- }
-}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c330046..4cf2909 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -879,7 +879,8 @@
mRankingHelper = new RankingHelper(getContext(),
new RankingWorkerHandler(mRankingThread.getLooper()),
extractorNames);
- mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper());
+ mConditionProviders = new ConditionProviders(getContext(), mHandler, mUserProfiles);
+ mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders);
mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@Override
public void onConfigChanged() {
@@ -900,8 +901,6 @@
importOldBlockDb();
mListeners = new NotificationListeners();
- mConditionProviders = new ConditionProviders(getContext(),
- mHandler, mUserProfiles, mZenModeHelper);
mStatusBar = getLocalService(StatusBarManagerInternal.class);
mStatusBar.setNotificationDelegate(mNotificationDelegate);
@@ -936,7 +935,7 @@
Settings.Global.DEVICE_PROVISIONED, 0)) {
mDisableNotificationEffects = true;
}
- mZenModeHelper.readZenModeFromSetting();
+ mZenModeHelper.initZenMode();
mInterruptionFilter = mZenModeHelper.getZenModeListenerInterruptionFilter();
mUserProfiles.updateCache(getContext());
@@ -1490,23 +1489,28 @@
}
@Override
+ public int getZenMode() {
+ return mZenModeHelper.getZenMode();
+ }
+
+ @Override
public ZenModeConfig getZenModeConfig() {
enforceSystemOrSystemUIOrVolume("INotificationManager.getZenModeConfig");
return mZenModeHelper.getConfig();
}
@Override
- public boolean setZenModeConfig(ZenModeConfig config) {
+ public boolean setZenModeConfig(ZenModeConfig config, String reason) {
checkCallerIsSystem();
- return mZenModeHelper.setConfig(config);
+ return mZenModeHelper.setConfig(config, reason);
}
@Override
- public void setZenMode(int mode) throws RemoteException {
+ public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
enforceSystemOrSystemUIOrVolume("INotificationManager.setZenMode");
final long identity = Binder.clearCallingIdentity();
try {
- mZenModeHelper.setZenMode(mode, "NotificationManager");
+ mZenModeHelper.setManualZenMode(mode, conditionId, reason);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1528,30 +1532,7 @@
@Override
public void requestZenModeConditions(IConditionListener callback, int relevance) {
enforceSystemOrSystemUIOrVolume("INotificationManager.requestZenModeConditions");
- mConditionProviders.requestZenModeConditions(callback, relevance);
- }
-
- @Override
- public void setZenModeCondition(Condition condition) {
- enforceSystemOrSystemUIOrVolume("INotificationManager.setZenModeCondition");
- final long identity = Binder.clearCallingIdentity();
- try {
- mConditionProviders.setZenModeCondition(condition, "binderCall");
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
- public void setAutomaticZenModeConditions(Uri[] conditionIds) {
- enforceSystemOrSystemUI("INotificationManager.setAutomaticZenModeConditions");
- mConditionProviders.setAutomaticZenModeConditions(conditionIds);
- }
-
- @Override
- public Condition[] getAutomaticZenModeConditions() {
- enforceSystemOrSystemUI("INotificationManager.getAutomaticZenModeConditions");
- return mConditionProviders.getAutomaticZenModeConditions();
+ mZenModeHelper.requestZenModeConditions(callback, relevance);
}
private void enforceSystemOrSystemUIOrVolume(String message) {
@@ -1603,7 +1584,7 @@
@Override
public boolean isSystemConditionProviderEnabled(String path) {
enforceSystemOrSystemUIOrVolume("INotificationManager.isSystemConditionProviderEnabled");
- return mConditionProviders.isSystemConditionProviderEnabled(path);
+ return mConditionProviders.isSystemProviderEnabled(path);
}
};
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index e029c58..4696771 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -28,6 +28,7 @@
import android.service.notification.StatusBarNotification;
import android.util.Log;
+import com.android.internal.logging.MetricsLogger;
import com.android.server.notification.NotificationManagerService.DumpFilter;
import java.io.PrintWriter;
@@ -56,8 +57,10 @@
// Guarded by synchronized(this).
private final Map<String, AggregatedStats> mStats = new HashMap<String, AggregatedStats>();
private final SQLiteLog mSQLiteLog;
+ private final Context mContext;
public NotificationUsageStats(Context context) {
+ mContext = context;
mSQLiteLog = ENABLE_SQLITE_LOG ? new SQLiteLog(context) : null;
}
@@ -103,6 +106,8 @@
* Called when the user dismissed the notification via the UI.
*/
public synchronized void registerDismissedByUser(NotificationRecord notification) {
+ MetricsLogger.histogram(mContext, "note_dismiss_longevity",
+ (int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
notification.stats.onDismiss();
for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
stats.numDismissedByUser++;
@@ -117,6 +122,8 @@
* Called when the user clicked the notification in the UI.
*/
public synchronized void registerClickedByUser(NotificationRecord notification) {
+ MetricsLogger.histogram(mContext, "note_click_longevity",
+ (int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
notification.stats.onClick();
for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
stats.numClickedByUser++;
diff --git a/services/core/java/com/android/server/notification/ScheduleCalendar.java b/services/core/java/com/android/server/notification/ScheduleCalendar.java
new file mode 100644
index 0000000..cea611d
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ScheduleCalendar.java
@@ -0,0 +1,117 @@
+/**
+ * Copyright (c) 2014, 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.server.notification;
+
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.util.ArraySet;
+
+import java.util.Calendar;
+import java.util.Objects;
+import java.util.TimeZone;
+
+public class ScheduleCalendar {
+ private final ArraySet<Integer> mDays = new ArraySet<Integer>();
+ private final Calendar mCalendar = Calendar.getInstance();
+
+ private ScheduleInfo mSchedule;
+
+ @Override
+ public String toString() {
+ return "ScheduleCalendar[mDays=" + mDays + "]";
+ }
+
+ public void setSchedule(ScheduleInfo schedule) {
+ if (Objects.equals(mSchedule, schedule)) return;
+ mSchedule = schedule;
+ updateDays();
+ }
+
+ public long nextScheduleStart(long time) {
+ if (mSchedule == null || mDays.size() == 0) return Long.MAX_VALUE;
+ final long start = getTime(time, mSchedule.startHour, mSchedule.startMinute);
+ for (int i = 0; i < Calendar.SATURDAY; i++) {
+ final long t = addDays(start, i);
+ if (t > time && isInSchedule(t)) {
+ return t;
+ }
+ }
+ return Long.MAX_VALUE;
+ }
+
+ public void setTimeZone(TimeZone tz) {
+ mCalendar.setTimeZone(tz);
+ }
+
+ public long getNextChangeTime(long now) {
+ if (mSchedule == null) return 0;
+ final long nextStart = getNextTime(now, mSchedule.startHour, mSchedule.startMinute);
+ final long nextEnd = getNextTime(now, mSchedule.endHour, mSchedule.endMinute);
+ return Math.min(nextStart, nextEnd);
+ }
+
+ private long getNextTime(long now, int hr, int min) {
+ final long time = getTime(now, hr, min);
+ return time <= now ? addDays(time, 1) : time;
+ }
+
+ private long getTime(long millis, int hour, int min) {
+ mCalendar.setTimeInMillis(millis);
+ mCalendar.set(Calendar.HOUR_OF_DAY, hour);
+ mCalendar.set(Calendar.MINUTE, min);
+ mCalendar.set(Calendar.SECOND, 0);
+ mCalendar.set(Calendar.MILLISECOND, 0);
+ return mCalendar.getTimeInMillis();
+ }
+
+ public boolean isInSchedule(long time) {
+ if (mSchedule == null || mDays.size() == 0) return false;
+ final long start = getTime(time, mSchedule.startHour, mSchedule.startMinute);
+ long end = getTime(time, mSchedule.endHour, mSchedule.endMinute);
+ if (end <= start) {
+ end = addDays(end, 1);
+ }
+ return isInSchedule(-1, time, start, end) || isInSchedule(0, time, start, end);
+ }
+
+ private boolean isInSchedule(int daysOffset, long time, long start, long end) {
+ final int n = Calendar.SATURDAY;
+ final int day = ((getDayOfWeek(time) - 1) + (daysOffset % n) + n) % n + 1;
+ start = addDays(start, daysOffset);
+ end = addDays(end, daysOffset);
+ return mDays.contains(day) && time >= start && time < end;
+ }
+
+ private int getDayOfWeek(long time) {
+ mCalendar.setTimeInMillis(time);
+ return mCalendar.get(Calendar.DAY_OF_WEEK);
+ }
+
+ private void updateDays() {
+ mDays.clear();
+ if (mSchedule != null && mSchedule.days != null) {
+ for (int i = 0; i < mSchedule.days.length; i++) {
+ mDays.add(mSchedule.days[i]);
+ }
+ }
+ }
+
+ private long addDays(long time, int days) {
+ mCalendar.setTimeInMillis(time);
+ mCalendar.add(Calendar.DATE, days);
+ return mCalendar.getTimeInMillis();
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
new file mode 100644
index 0000000..c997e45
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
@@ -0,0 +1,238 @@
+/*
+ * 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.server.notification;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.IConditionProvider;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.server.notification.NotificationManagerService.DumpFilter;
+
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Built-in zen condition provider for daily scheduled time-based conditions.
+ */
+public class ScheduleConditionProvider extends SystemConditionProviderService {
+ private static final String TAG = "ScheduleConditions";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ public static final ComponentName COMPONENT =
+ new ComponentName("android", ScheduleConditionProvider.class.getName());
+ private static final String NOT_SHOWN = "...";
+ private static final String ACTION_EVALUATE = TAG + ".EVALUATE";
+ private static final int REQUEST_CODE_EVALUATE = 1;
+ private static final String EXTRA_TIME = "time";
+
+ private final Context mContext = this;
+ private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
+
+ private boolean mConnected;
+ private boolean mRegistered;
+
+ public ScheduleConditionProvider() {
+ if (DEBUG) Slog.d(TAG, "new ScheduleConditionProvider()");
+ }
+
+ @Override
+ public ComponentName getComponent() {
+ return COMPONENT;
+ }
+
+ @Override
+ public boolean isValidConditionid(Uri id) {
+ return ZenModeConfig.isValidScheduleConditionId(id);
+ }
+
+ @Override
+ public void dump(PrintWriter pw, DumpFilter filter) {
+ pw.println(" ScheduleConditionProvider:");
+ pw.print(" mConnected="); pw.println(mConnected);
+ pw.print(" mRegistered="); pw.println(mRegistered);
+ pw.println(" mSubscriptions=");
+ final long now = System.currentTimeMillis();
+ for (Uri conditionId : mSubscriptions) {
+ pw.print(" ");
+ pw.print(meetsSchedule(conditionId, now) ? "* " : " ");
+ pw.println(conditionId);
+ }
+ }
+
+ @Override
+ public void onConnected() {
+ if (DEBUG) Slog.d(TAG, "onConnected");
+ mConnected = true;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (DEBUG) Slog.d(TAG, "onDestroy");
+ mConnected = false;
+ }
+
+ @Override
+ public void onRequestConditions(int relevance) {
+ if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
+ // does not advertise conditions
+ }
+
+ @Override
+ public void onSubscribe(Uri conditionId) {
+ if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
+ if (!ZenModeConfig.isValidScheduleConditionId(conditionId)) {
+ notifyCondition(conditionId, Condition.STATE_FALSE, "badCondition");
+ return;
+ }
+ mSubscriptions.add(conditionId);
+ evaluateSubscriptions();
+ }
+
+ @Override
+ public void onUnsubscribe(Uri conditionId) {
+ if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
+ mSubscriptions.remove(conditionId);
+ evaluateSubscriptions();
+ }
+
+ @Override
+ public void attachBase(Context base) {
+ attachBaseContext(base);
+ }
+
+ @Override
+ public IConditionProvider asInterface() {
+ return (IConditionProvider) onBind(null);
+ }
+
+ private void evaluateSubscriptions() {
+ setRegistered(!mSubscriptions.isEmpty());
+ final long now = System.currentTimeMillis();
+ long nextAlarmTime = 0;
+ for (Uri conditionId : mSubscriptions) {
+ final ScheduleCalendar cal = toScheduleCalendar(conditionId);
+ if (cal != null && cal.isInSchedule(now)) {
+ notifyCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule");
+ } else {
+ notifyCondition(conditionId, Condition.STATE_FALSE, "!meetsSchedule");
+ }
+ if (cal != null) {
+ final long nextChangeTime = cal.getNextChangeTime(now);
+ if (nextChangeTime > 0 && nextChangeTime > now) {
+ if (nextAlarmTime == 0 || nextChangeTime < nextAlarmTime) {
+ nextAlarmTime = nextChangeTime;
+ }
+ }
+ }
+ }
+ updateAlarm(now, nextAlarmTime);
+ }
+
+ private void updateAlarm(long now, long time) {
+ final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
+ REQUEST_CODE_EVALUATE,
+ new Intent(ACTION_EVALUATE)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(EXTRA_TIME, time),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ alarms.cancel(pendingIntent);
+ if (time > now) {
+ if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s",
+ ts(time), formatDuration(time - now), ts(now)));
+ alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
+ } else {
+ if (DEBUG) Slog.d(TAG, "Not scheduling evaluate");
+ }
+ }
+
+ private static String ts(long time) {
+ return new Date(time) + " (" + time + ")";
+ }
+
+ private static String formatDuration(long millis) {
+ final StringBuilder sb = new StringBuilder();
+ TimeUtils.formatDuration(millis, sb);
+ return sb.toString();
+ }
+
+ private static boolean meetsSchedule(Uri conditionId, long time) {
+ final ScheduleCalendar cal = toScheduleCalendar(conditionId);
+ return cal != null && cal.isInSchedule(time);
+ }
+
+ private static ScheduleCalendar toScheduleCalendar(Uri conditionId) {
+ final ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(conditionId);
+ if (schedule == null || schedule.days == null || schedule.days.length == 0) return null;
+ final ScheduleCalendar sc = new ScheduleCalendar();
+ sc.setSchedule(schedule);
+ sc.setTimeZone(TimeZone.getDefault());
+ return sc;
+ }
+
+ private void setRegistered(boolean registered) {
+ if (mRegistered == registered) return;
+ if (DEBUG) Slog.d(TAG, "setRegistered " + registered);
+ mRegistered = registered;
+ if (mRegistered) {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ filter.addAction(ACTION_EVALUATE);
+ registerReceiver(mReceiver, filter);
+ } else {
+ unregisterReceiver(mReceiver);
+ }
+ }
+
+ private void notifyCondition(Uri conditionId, int state, String reason) {
+ if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state)
+ + " reason=" + reason);
+ notifyCondition(createCondition(conditionId, state));
+ }
+
+ private Condition createCondition(Uri id, int state) {
+ final String summary = NOT_SHOWN;
+ final String line1 = NOT_SHOWN;
+ final String line2 = NOT_SHOWN;
+ return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS);
+ }
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction());
+ evaluateSubscriptions();
+ }
+ };
+
+}
diff --git a/services/core/java/com/android/server/notification/SystemConditionProviderService.java b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
new file mode 100644
index 0000000..a217623
--- /dev/null
+++ b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
@@ -0,0 +1,36 @@
+/*
+ * 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.server.notification;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.service.notification.ConditionProviderService;
+import android.service.notification.IConditionProvider;
+
+import com.android.server.notification.NotificationManagerService.DumpFilter;
+
+import java.io.PrintWriter;
+
+public abstract class SystemConditionProviderService extends ConditionProviderService {
+
+ abstract public void dump(PrintWriter pw, DumpFilter filter);
+ abstract public void attachBase(Context context);
+ abstract public IConditionProvider asInterface();
+ abstract public ComponentName getComponent();
+ abstract public boolean isValidConditionid(Uri id);
+}
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 5eb318b..10f1696 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -34,6 +34,7 @@
import android.util.Log;
import android.util.LruCache;
import android.util.Slog;
+import com.android.internal.logging.MetricsLogger;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -244,6 +245,7 @@
if (pendingLookups.isEmpty()) {
if (INFO) Slog.i(TAG, "final affinity: " + affinity);
+ if (affinity != NONE) MetricsLogger.count(mBaseContext, "note_with_people", 1);
return null;
}
@@ -453,6 +455,8 @@
Slog.d(TAG, "Validation finished in " + (System.currentTimeMillis() - timeStartMs) +
"ms");
}
+
+ if (mContactAffinity != NONE) MetricsLogger.count(mBaseContext, "note_with_people", 1);
}
@Override
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
new file mode 100644
index 0000000..67a2a54
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -0,0 +1,144 @@
+/**
+ * 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.server.notification;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.IConditionListener;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+public class ZenModeConditions implements ConditionProviders.Callback {
+ private static final String TAG = ZenModeHelper.TAG;
+ private static final boolean DEBUG = ZenModeHelper.DEBUG;
+
+ private final ZenModeHelper mHelper;
+ private final ConditionProviders mConditionProviders;
+ private final ArrayMap<Uri, ComponentName> mSubscriptions = new ArrayMap<>();
+
+ private CountdownConditionProvider mCountdown;
+ private ScheduleConditionProvider mSchedule;
+
+ public ZenModeConditions(ZenModeHelper helper, ConditionProviders conditionProviders) {
+ mHelper = helper;
+ mConditionProviders = conditionProviders;
+ if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.COUNTDOWN_PATH)) {
+ mCountdown = new CountdownConditionProvider();
+ mConditionProviders.addSystemProvider(mCountdown);
+ }
+ if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.SCHEDULE_PATH)) {
+ mSchedule = new ScheduleConditionProvider();
+ mConditionProviders.addSystemProvider(mSchedule);
+ }
+ mConditionProviders.setCallback(this);
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("mSubscriptions="); pw.println(mSubscriptions);
+ }
+
+ public void requestConditions(IConditionListener callback, int relevance) {
+ mConditionProviders.requestConditions(callback, relevance);
+ }
+
+ public void evaluateConfig(ZenModeConfig config) {
+ if (config == null) return;
+ if (config.manualRule != null && !config.manualRule.isTrueOrUnknown()) {
+ if (DEBUG) Log.d(TAG, "evaluateConfig: clearing manual rule");
+ config.manualRule = null;
+ }
+ final ArraySet<Uri> current = new ArraySet<>();
+ evaluateRule(config.manualRule, current);
+ for (ZenRule automaticRule : config.automaticRules.values()) {
+ evaluateRule(automaticRule, current);
+ }
+ final int N = mSubscriptions.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final Uri id = mSubscriptions.keyAt(i);
+ final ComponentName component = mSubscriptions.valueAt(i);
+ if (!current.contains(id)) {
+ mConditionProviders.unsubscribeIfNecessary(component, id);
+ mSubscriptions.removeAt(i);
+ }
+ }
+ }
+
+ private void evaluateRule(ZenRule rule, ArraySet<Uri> current) {
+ if (rule == null || rule.conditionId == null) return;
+ final Uri id = rule.conditionId;
+ for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) {
+ if (sp.isValidConditionid(id)) {
+ mConditionProviders.ensureRecordExists(sp.getComponent(), id, sp.asInterface());
+ rule.component = sp.getComponent();
+ }
+ }
+ current.add(id);
+ if (mConditionProviders.subscribeIfNecessary(rule.component, rule.conditionId)) {
+ mSubscriptions.put(rule.conditionId, rule.component);
+ }
+ }
+
+ @Override
+ public void onBootComplete() {
+ // noop
+ }
+
+ @Override
+ public void onUserSwitched() {
+ // noop
+ }
+
+ @Override
+ public void onConditionChanged(Uri id, Condition condition) {
+ if (DEBUG) Log.d(TAG, "onConditionChanged " + id + " " + condition);
+ ZenModeConfig config = mHelper.getConfig();
+ if (config == null) return;
+ config = config.copy();
+ boolean updated = updateCondition(id, condition, config.manualRule);
+ for (ZenRule automaticRule : config.automaticRules.values()) {
+ updated |= updateCondition(id, condition, automaticRule);
+ updated |= updateSnoozing(automaticRule);
+ }
+ if (updated) {
+ mHelper.setConfig(config, "conditionChanged");
+ }
+ }
+
+ private boolean updateSnoozing(ZenRule rule) {
+ if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) {
+ rule.snoozing = false;
+ if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean updateCondition(Uri id, Condition condition, ZenRule rule) {
+ if (id == null || rule == null || rule.conditionId == null) return false;
+ if (!rule.conditionId.equals(id)) return false;
+ if (Objects.equals(condition, rule.condition)) return false;
+ rule.condition = condition;
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
new file mode 100644
index 0000000..2aaeb9d
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -0,0 +1,279 @@
+/**
+ * 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.server.notification;
+
+import android.app.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
+import android.service.notification.ZenModeConfig;
+import android.telecom.TelecomManager;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.Objects;
+
+public class ZenModeFiltering {
+ private static final String TAG = ZenModeHelper.TAG;
+ private static final boolean DEBUG = ZenModeHelper.DEBUG;
+
+ static final RepeatCallers REPEAT_CALLERS = new RepeatCallers();
+
+ private final Context mContext;
+
+ private ComponentName mDefaultPhoneApp;
+
+ public ZenModeFiltering(Context context) {
+ mContext = context;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp);
+ pw.print(prefix); pw.print("RepeatCallers.mThresholdMinutes=");
+ pw.println(REPEAT_CALLERS.mThresholdMinutes);
+ synchronized (REPEAT_CALLERS) {
+ if (!REPEAT_CALLERS.mCalls.isEmpty()) {
+ pw.print(prefix); pw.println("RepeatCallers.mCalls=");
+ for (int i = 0; i < REPEAT_CALLERS.mCalls.size(); i++) {
+ pw.print(prefix); pw.print(" ");
+ pw.print(REPEAT_CALLERS.mCalls.keyAt(i));
+ pw.print(" at ");
+ pw.println(ts(REPEAT_CALLERS.mCalls.valueAt(i)));
+ }
+ }
+ }
+ }
+
+ private static String ts(long time) {
+ return new Date(time) + " (" + time + ")";
+ }
+
+ /**
+ * @param extras extras of the notification with EXTRA_PEOPLE populated
+ * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response
+ * @param timeoutAffinity affinity to return when the timeout specified via
+ * <code>contactsTimeoutMs</code> is hit
+ */
+ public static boolean matchesCallFilter(Context context, int zen, ZenModeConfig config,
+ UserHandle userHandle, Bundle extras, ValidateNotificationPeople validator,
+ int contactsTimeoutMs, float timeoutAffinity) {
+ if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
+ if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm
+ if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
+ if (config.allowRepeatCallers && REPEAT_CALLERS.isRepeat(context, extras)) return true;
+ if (!config.allowCalls) return false; // no other calls get through
+ if (validator != null) {
+ final float contactAffinity = validator.getContactAffinity(userHandle, extras,
+ contactsTimeoutMs, timeoutAffinity);
+ return audienceMatches(config, contactAffinity);
+ }
+ }
+ return true;
+ }
+
+ private static Bundle extras(NotificationRecord record) {
+ return record != null && record.sbn != null && record.sbn.getNotification() != null
+ ? record.sbn.getNotification().extras : null;
+ }
+
+ public boolean shouldIntercept(int zen, ZenModeConfig config, NotificationRecord record) {
+ if (isSystem(record)) {
+ return false;
+ }
+ switch (zen) {
+ case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ // #notevenalarms
+ ZenLog.traceIntercepted(record, "none");
+ return true;
+ case Global.ZEN_MODE_ALARMS:
+ if (isAlarm(record)) {
+ // Alarms only
+ return false;
+ }
+ ZenLog.traceIntercepted(record, "alarmsOnly");
+ return true;
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ if (isAlarm(record)) {
+ // Alarms are always priority
+ return false;
+ }
+ // allow user-prioritized packages through in priority mode
+ if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
+ ZenLog.traceNotIntercepted(record, "priorityApp");
+ return false;
+ }
+ if (isCall(record)) {
+ if (config.allowRepeatCallers
+ && REPEAT_CALLERS.isRepeat(mContext, extras(record))) {
+ ZenLog.traceNotIntercepted(record, "repeatCaller");
+ return false;
+ }
+ if (!config.allowCalls) {
+ ZenLog.traceIntercepted(record, "!allowCalls");
+ return true;
+ }
+ return shouldInterceptAudience(config, record);
+ }
+ if (isMessage(record)) {
+ if (!config.allowMessages) {
+ ZenLog.traceIntercepted(record, "!allowMessages");
+ return true;
+ }
+ return shouldInterceptAudience(config, record);
+ }
+ if (isEvent(record)) {
+ if (!config.allowEvents) {
+ ZenLog.traceIntercepted(record, "!allowEvents");
+ return true;
+ }
+ return false;
+ }
+ if (isReminder(record)) {
+ if (!config.allowReminders) {
+ ZenLog.traceIntercepted(record, "!allowReminders");
+ return true;
+ }
+ return false;
+ }
+ ZenLog.traceIntercepted(record, "!priority");
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static boolean shouldInterceptAudience(ZenModeConfig config,
+ NotificationRecord record) {
+ if (!audienceMatches(config, record.getContactAffinity())) {
+ ZenLog.traceIntercepted(record, "!audienceMatches");
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean isSystem(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_SYSTEM);
+ }
+
+ private static boolean isAlarm(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_ALARM)
+ || record.isAudioStream(AudioManager.STREAM_ALARM)
+ || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM);
+ }
+
+ private static boolean isEvent(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_EVENT);
+ }
+
+ private static boolean isReminder(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_REMINDER);
+ }
+
+ public boolean isCall(NotificationRecord record) {
+ return record != null && (isDefaultPhoneApp(record.sbn.getPackageName())
+ || record.isCategory(Notification.CATEGORY_CALL));
+ }
+
+ private boolean isDefaultPhoneApp(String pkg) {
+ if (mDefaultPhoneApp == null) {
+ final TelecomManager telecomm =
+ (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+ mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null;
+ if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp);
+ }
+ return pkg != null && mDefaultPhoneApp != null
+ && pkg.equals(mDefaultPhoneApp.getPackageName());
+ }
+
+ @SuppressWarnings("deprecation")
+ private boolean isDefaultMessagingApp(NotificationRecord record) {
+ final int userId = record.getUserId();
+ if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
+ final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(),
+ Secure.SMS_DEFAULT_APPLICATION, userId);
+ return Objects.equals(defaultApp, record.sbn.getPackageName());
+ }
+
+ private boolean isMessage(NotificationRecord record) {
+ return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
+ }
+
+ private static boolean audienceMatches(ZenModeConfig config, float contactAffinity) {
+ switch (config.allowFrom) {
+ case ZenModeConfig.SOURCE_ANYONE:
+ return true;
+ case ZenModeConfig.SOURCE_CONTACT:
+ return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
+ case ZenModeConfig.SOURCE_STAR:
+ return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
+ default:
+ Slog.w(TAG, "Encountered unknown source: " + config.allowFrom);
+ return true;
+ }
+ }
+
+ private static class RepeatCallers {
+ private final ArrayMap<String, Long> mCalls = new ArrayMap<>();
+ private int mThresholdMinutes;
+
+ private synchronized boolean isRepeat(Context context, Bundle extras) {
+ if (mThresholdMinutes <= 0) {
+ mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer
+ .config_zen_repeat_callers_threshold);
+ }
+ if (mThresholdMinutes <= 0 || extras == null) return false;
+ final String peopleString = peopleString(extras);
+ if (peopleString == null) return false;
+ final long now = System.currentTimeMillis();
+ final int N = mCalls.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final long time = mCalls.valueAt(i);
+ if (time > now || (now - time) > mThresholdMinutes * 1000 * 60) {
+ mCalls.removeAt(i);
+ }
+ }
+ final boolean isRepeat = mCalls.containsKey(peopleString);
+ mCalls.put(peopleString, now);
+ return isRepeat;
+ }
+
+ private static String peopleString(Bundle extras) {
+ final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
+ if (extraPeople == null || extraPeople.length == 0) return null;
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < extraPeople.length; i++) {
+ String extraPerson = extraPeople[i];
+ if (extraPerson == null) continue;
+ extraPerson = extraPerson.trim();
+ if (extraPerson.isEmpty()) continue;
+ if (sb.length() > 0) {
+ sb.append('|');
+ }
+ sb.append(extraPerson);
+ }
+ return sb.length() == 0 ? null : sb.toString();
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 1775df2..e5925fe 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -21,14 +21,12 @@
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import android.app.AppOpsManager;
-import android.app.Notification;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
-import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.VolumePolicy;
@@ -39,12 +37,13 @@
import android.os.Message;
import android.os.UserHandle;
import android.provider.Settings.Global;
-import android.provider.Settings.Secure;
+import android.service.notification.IConditionListener;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
-import android.telecom.TelecomManager;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.util.ArraySet;
import android.util.Log;
-import android.util.Slog;
import com.android.internal.R;
import com.android.server.LocalServices;
@@ -58,14 +57,13 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Objects;
/**
* NotificationManagerService helper for functionality related to zen mode.
*/
-public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
- private static final String TAG = "ZenModeHelper";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+public class ZenModeHelper {
+ static final String TAG = "ZenModeHelper";
+ static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final Context mContext;
private final H mHandler;
@@ -73,38 +71,46 @@
private final AppOpsManager mAppOps;
private final ZenModeConfig mDefaultConfig;
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+ private final ZenModeFiltering mFiltering;
+ private final RingerModeDelegate mRingerModeDelegate = new RingerModeDelegate();
+ private final ZenModeConditions mConditions;
- private ComponentName mDefaultPhoneApp;
private int mZenMode;
private ZenModeConfig mConfig;
private AudioManagerInternal mAudioManager;
private int mPreviousRingerMode = -1;
private boolean mEffectsSuppressed;
- public ZenModeHelper(Context context, Looper looper) {
+ public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders) {
mContext = context;
mHandler = new H(looper);
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mDefaultConfig = readDefaultConfig(context.getResources());
+ appendDefaultScheduleRules(mDefaultConfig);
mConfig = mDefaultConfig;
mSettingsObserver = new SettingsObserver(mHandler);
mSettingsObserver.observe();
+ mFiltering = new ZenModeFiltering(mContext);
+ mConditions = new ZenModeConditions(this, conditionProviders);
}
- public static ZenModeConfig readDefaultConfig(Resources resources) {
- XmlResourceParser parser = null;
- try {
- parser = resources.getXml(R.xml.default_zen_mode_config);
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- final ZenModeConfig config = ZenModeConfig.readXml(parser);
- if (config != null) return config;
- }
- } catch (Exception e) {
- Slog.w(TAG, "Error reading default zen mode config from resource", e);
- } finally {
- IoUtils.closeQuietly(parser);
- }
- return new ZenModeConfig();
+ @Override
+ public String toString() {
+ return TAG;
+ }
+
+ public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
+ ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
+ return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConfig, userHandle, extras,
+ validator, contactsTimeoutMs, timeoutAffinity);
+ }
+
+ public boolean isCall(NotificationRecord record) {
+ return mFiltering.isCall(record);
+ }
+
+ public boolean shouldIntercept(NotificationRecord record) {
+ return mFiltering.shouldIntercept(mZenMode, mConfig, record);
}
public void addCallback(Callback callback) {
@@ -115,48 +121,32 @@
mCallbacks.remove(callback);
}
+ public void initZenMode() {
+ if (DEBUG) Log.d(TAG, "initZenMode");
+ evaluateZenMode("init", true /*setRingerMode*/);
+ }
+
public void onSystemReady() {
+ if (DEBUG) Log.d(TAG, "onSystemReady");
mAudioManager = LocalServices.getService(AudioManagerInternal.class);
if (mAudioManager != null) {
- mAudioManager.setRingerModeDelegate(this);
+ mAudioManager.setRingerModeDelegate(mRingerModeDelegate);
}
}
+ public void requestZenModeConditions(IConditionListener callback, int relevance) {
+ mConditions.requestConditions(callback, relevance);
+ }
+
public int getZenModeListenerInterruptionFilter() {
- switch (mZenMode) {
- case Global.ZEN_MODE_OFF:
- return NotificationListenerService.INTERRUPTION_FILTER_ALL;
- case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
- case Global.ZEN_MODE_ALARMS:
- return NotificationListenerService.INTERRUPTION_FILTER_ALARMS;
- case Global.ZEN_MODE_NO_INTERRUPTIONS:
- return NotificationListenerService.INTERRUPTION_FILTER_NONE;
- default:
- return 0;
- }
- }
-
- private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter,
- int defValue) {
- switch (listenerInterruptionFilter) {
- case NotificationListenerService.INTERRUPTION_FILTER_ALL:
- return Global.ZEN_MODE_OFF;
- case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY:
- return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- case NotificationListenerService.INTERRUPTION_FILTER_ALARMS:
- return Global.ZEN_MODE_ALARMS;
- case NotificationListenerService.INTERRUPTION_FILTER_NONE:
- return Global.ZEN_MODE_NO_INTERRUPTIONS;
- default:
- return defValue;
- }
+ return getZenModeListenerInterruptionFilter(mZenMode);
}
public void requestFromListener(ComponentName name, int interruptionFilter) {
final int newZen = zenModeFromListenerInterruptionFilter(interruptionFilter, -1);
if (newZen != -1) {
- setZenMode(newZen, "listener:" + (name != null ? name.flattenToShortString() : null));
+ setManualZenMode(newZen, null,
+ "listener:" + (name != null ? name.flattenToShortString() : null));
}
}
@@ -166,100 +156,144 @@
applyRestrictions();
}
- public boolean shouldIntercept(NotificationRecord record) {
- if (isSystem(record)) {
- return false;
- }
- switch (mZenMode) {
- case Global.ZEN_MODE_NO_INTERRUPTIONS:
- // #notevenalarms
- ZenLog.traceIntercepted(record, "none");
- return true;
- case Global.ZEN_MODE_ALARMS:
- if (isAlarm(record)) {
- // Alarms only
- return false;
- }
- ZenLog.traceIntercepted(record, "alarmsOnly");
- return true;
- case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- if (isAlarm(record)) {
- // Alarms are always priority
- return false;
- }
- // allow user-prioritized packages through in priority mode
- if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
- ZenLog.traceNotIntercepted(record, "priorityApp");
- return false;
- }
- if (isCall(record)) {
- if (!mConfig.allowCalls) {
- ZenLog.traceIntercepted(record, "!allowCalls");
- return true;
- }
- return shouldInterceptAudience(record);
- }
- if (isMessage(record)) {
- if (!mConfig.allowMessages) {
- ZenLog.traceIntercepted(record, "!allowMessages");
- return true;
- }
- return shouldInterceptAudience(record);
- }
- if (isEvent(record)) {
- if (!mConfig.allowEvents) {
- ZenLog.traceIntercepted(record, "!allowEvents");
- return true;
- }
- return false;
- }
- if (isReminder(record)) {
- if (!mConfig.allowReminders) {
- ZenLog.traceIntercepted(record, "!allowReminders");
- return true;
- }
- return false;
- }
- ZenLog.traceIntercepted(record, "!priority");
- return true;
- default:
- return false;
- }
- }
-
- private boolean shouldInterceptAudience(NotificationRecord record) {
- if (!audienceMatches(record.getContactAffinity())) {
- ZenLog.traceIntercepted(record, "!audienceMatches");
- return true;
- }
- return false;
- }
-
public int getZenMode() {
return mZenMode;
}
- public void setZenMode(int zenMode, String reason) {
- setZenMode(zenMode, reason, true);
+ public void setManualZenMode(int zenMode, Uri conditionId, String reason) {
+ setManualZenMode(zenMode, conditionId, reason, true /*setRingerMode*/);
}
- private void setZenMode(int zenMode, String reason, boolean setRingerMode) {
- ZenLog.traceSetZenMode(zenMode, reason);
- if (mZenMode == zenMode) return;
- ZenLog.traceUpdateZenMode(mZenMode, zenMode);
- mZenMode = zenMode;
- Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, mZenMode);
+ private void setManualZenMode(int zenMode, Uri conditionId, String reason,
+ boolean setRingerMode) {
+ if (mConfig == null) return;
+ if (!Global.isValidZenMode(zenMode)) return;
+ if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
+ + " conditionId=" + conditionId + " reason=" + reason
+ + " setRingerMode=" + setRingerMode);
+ final ZenModeConfig newConfig = mConfig.copy();
+ if (zenMode == Global.ZEN_MODE_OFF) {
+ newConfig.manualRule = null;
+ for (ZenRule automaticRule : newConfig.automaticRules.values()) {
+ if (automaticRule.isTrueOrUnknown()) {
+ automaticRule.snoozing = true;
+ }
+ }
+ } else {
+ final ZenRule newRule = new ZenRule();
+ newRule.enabled = true;
+ newRule.zenMode = zenMode;
+ newRule.conditionId = conditionId;
+ newConfig.manualRule = newRule;
+ }
+ setConfig(newConfig, reason, setRingerMode);
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.print("mZenMode=");
+ pw.println(Global.zenModeToString(mZenMode));
+ dump(pw, prefix, "mConfig", mConfig);
+ dump(pw, prefix, "mDefaultConfig", mDefaultConfig);
+ pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
+ pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
+ mFiltering.dump(pw, prefix);
+ mConditions.dump(pw, prefix);
+ }
+
+ private static void dump(PrintWriter pw, String prefix, String var, ZenModeConfig config) {
+ pw.print(prefix); pw.print(var); pw.print('=');
+ if (config == null) {
+ pw.println(config);
+ return;
+ }
+ pw.printf("allow(calls=%s,repeatCallers=%s,events=%s,from=%s,messages=%s,reminders=%s)\n",
+ config.allowCalls, config.allowRepeatCallers, config.allowEvents, config.allowFrom,
+ config.allowMessages, config.allowReminders);
+ pw.print(prefix); pw.print(" manualRule="); pw.println(config.manualRule);
+ if (config.automaticRules.isEmpty()) return;
+ final int N = config.automaticRules.size();
+ for (int i = 0; i < N; i++) {
+ pw.print(prefix); pw.print(i == 0 ? " automaticRules=" : " ");
+ pw.println(config.automaticRules.valueAt(i));
+ }
+ }
+
+ public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
+ final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration);
+ if (config != null) {
+ if (DEBUG) Log.d(TAG, "readXml");
+ setConfig(config, "readXml");
+ }
+ }
+
+ public void writeXml(XmlSerializer out) throws IOException {
+ mConfig.writeXml(out);
+ }
+
+ public ZenModeConfig getConfig() {
+ return mConfig;
+ }
+
+ public boolean setConfig(ZenModeConfig config, String reason) {
+ return setConfig(config, reason, true /*setRingerMode*/);
+ }
+
+ private boolean setConfig(ZenModeConfig config, String reason, boolean setRingerMode) {
+ if (config == null || !config.isValid()) {
+ Log.w(TAG, "Invalid config in setConfig; " + config);
+ return false;
+ }
+ mConditions.evaluateConfig(config); // may modify config
+ if (config.equals(mConfig)) return true;
+ if (DEBUG) Log.d(TAG, "setConfig reason=" + reason);
+ ZenLog.traceConfig(mConfig, config);
+ mConfig = config;
+ dispatchOnConfigChanged();
+ final String val = Integer.toString(mConfig.hashCode());
+ Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
+ if (!evaluateZenMode(reason, setRingerMode)) {
+ applyRestrictions(); // evaluateZenMode will also apply restrictions if changed
+ }
+ return true;
+ }
+
+ private int getZenModeSetting() {
+ return Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, Global.ZEN_MODE_OFF);
+ }
+
+ private void setZenModeSetting(int zen) {
+ Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen);
+ }
+
+ private boolean evaluateZenMode(String reason, boolean setRingerMode) {
+ if (DEBUG) Log.d(TAG, "evaluateZenMode");
+ final ArraySet<ZenRule> automaticRules = new ArraySet<ZenRule>();
+ final int zen = computeZenMode(automaticRules);
+ if (zen == mZenMode) return false;
+ ZenLog.traceSetZenMode(zen, reason);
+ mZenMode = zen;
+ setZenModeSetting(mZenMode);
if (setRingerMode) {
applyZenToRingerMode();
}
applyRestrictions();
mHandler.postDispatchOnZenModeChanged();
+ return true;
}
- public void readZenModeFromSetting() {
- final int newMode = Global.getInt(mContext.getContentResolver(),
- Global.ZEN_MODE, Global.ZEN_MODE_OFF);
- setZenMode(newMode, "setting");
+ private int computeZenMode(ArraySet<ZenRule> automaticRulesOut) {
+ if (mConfig == null) return Global.ZEN_MODE_OFF;
+ if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
+ int zen = Global.ZEN_MODE_OFF;
+ for (ZenRule automaticRule : mConfig.automaticRules.values()) {
+ if (automaticRule.enabled && !automaticRule.snoozing
+ && automaticRule.isTrueOrUnknown()) {
+ if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
+ zen = automaticRule.zenMode;
+ }
+ }
+ }
+ return zen;
}
private void applyRestrictions() {
@@ -270,7 +304,8 @@
applyRestrictions(muteNotifications, USAGE_NOTIFICATION);
// call restrictions
- final boolean muteCalls = zen && !mConfig.allowCalls || mEffectsSuppressed;
+ final boolean muteCalls = zen && !mConfig.allowCalls && !mConfig.allowRepeatCallers
+ || mEffectsSuppressed;
applyRestrictions(muteCalls, USAGE_NOTIFICATION_RINGTONE);
// alarm restrictions
@@ -288,43 +323,6 @@
exceptionPackages);
}
- public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix); pw.print("mZenMode=");
- pw.println(Global.zenModeToString(mZenMode));
- pw.print(prefix); pw.print("mConfig="); pw.println(mConfig);
- pw.print(prefix); pw.print("mDefaultConfig="); pw.println(mDefaultConfig);
- pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
- pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp);
- pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
- }
-
- public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
- final ZenModeConfig config = ZenModeConfig.readXml(parser);
- if (config != null) {
- setConfig(config);
- }
- }
-
- public void writeXml(XmlSerializer out) throws IOException {
- mConfig.writeXml(out);
- }
-
- public ZenModeConfig getConfig() {
- return mConfig;
- }
-
- public boolean setConfig(ZenModeConfig config) {
- if (config == null || !config.isValid()) return false;
- if (config.equals(mConfig)) return true;
- ZenLog.traceConfig(mConfig, config);
- mConfig = config;
- dispatchOnConfigChanged();
- final String val = Integer.toString(mConfig.hashCode());
- Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
- applyRestrictions();
- return true;
- }
-
private void applyZenToRingerMode() {
if (mAudioManager == null) return;
// force the ringer mode into compliance
@@ -352,81 +350,6 @@
}
}
- @Override // RingerModeDelegate
- public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
- int ringerModeExternal, VolumePolicy policy) {
- final boolean isChange = ringerModeOld != ringerModeNew;
-
- int ringerModeExternalOut = ringerModeNew;
-
- int newZen = -1;
- switch (ringerModeNew) {
- case AudioManager.RINGER_MODE_SILENT:
- if (isChange && policy.doNotDisturbWhenSilent) {
- if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS
- && mZenMode != Global.ZEN_MODE_ALARMS) {
- newZen = Global.ZEN_MODE_NO_INTERRUPTIONS;
- }
- }
- break;
- case AudioManager.RINGER_MODE_VIBRATE:
- case AudioManager.RINGER_MODE_NORMAL:
- if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT
- && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
- || mZenMode == Global.ZEN_MODE_ALARMS)) {
- newZen = Global.ZEN_MODE_OFF;
- } else if (mZenMode != Global.ZEN_MODE_OFF) {
- ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
- }
- break;
- }
- if (newZen != -1) {
- setZenMode(newZen, "ringerModeInternal", false /*setRingerMode*/);
- }
-
- if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
- ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
- ringerModeExternal, ringerModeExternalOut);
- }
- return ringerModeExternalOut;
- }
-
- @Override // RingerModeDelegate
- public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
- int ringerModeInternal, VolumePolicy policy) {
- int ringerModeInternalOut = ringerModeNew;
- final boolean isChange = ringerModeOld != ringerModeNew;
- final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
-
- int newZen = -1;
- switch (ringerModeNew) {
- case AudioManager.RINGER_MODE_SILENT:
- if (isChange) {
- if (mZenMode == Global.ZEN_MODE_OFF) {
- newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- }
- ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE
- : AudioManager.RINGER_MODE_NORMAL;
- } else {
- ringerModeInternalOut = ringerModeInternal;
- }
- break;
- case AudioManager.RINGER_MODE_VIBRATE:
- case AudioManager.RINGER_MODE_NORMAL:
- if (mZenMode != Global.ZEN_MODE_OFF) {
- newZen = Global.ZEN_MODE_OFF;
- }
- break;
- }
- if (newZen != -1) {
- setZenMode(newZen, "ringerModeExternal", false /*setRingerMode*/);
- }
-
- ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller, ringerModeInternal,
- ringerModeInternalOut);
- return ringerModeInternalOut;
- }
-
private void dispatchOnConfigChanged() {
for (Callback callback : mCallbacks) {
callback.onConfigChanged();
@@ -439,94 +362,210 @@
}
}
- private static boolean isSystem(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_SYSTEM);
- }
-
- private static boolean isAlarm(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_ALARM)
- || record.isAudioStream(AudioManager.STREAM_ALARM)
- || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM);
- }
-
- private static boolean isEvent(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_EVENT);
- }
-
- private static boolean isReminder(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_REMINDER);
- }
-
- public boolean isCall(NotificationRecord record) {
- return record != null && (isDefaultPhoneApp(record.sbn.getPackageName())
- || record.isCategory(Notification.CATEGORY_CALL));
- }
-
- private boolean isDefaultPhoneApp(String pkg) {
- if (mDefaultPhoneApp == null) {
- final TelecomManager telecomm =
- (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
- mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null;
- if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp);
- }
- return pkg != null && mDefaultPhoneApp != null
- && pkg.equals(mDefaultPhoneApp.getPackageName());
- }
-
- private boolean isDefaultMessagingApp(NotificationRecord record) {
- final int userId = record.getUserId();
- if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
- final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(),
- Secure.SMS_DEFAULT_APPLICATION, userId);
- return Objects.equals(defaultApp, record.sbn.getPackageName());
- }
-
- private boolean isMessage(NotificationRecord record) {
- return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
- }
-
- /**
- * @param extras extras of the notification with EXTRA_PEOPLE populated
- * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response
- * @param timeoutAffinity affinity to return when the timeout specified via
- * <code>contactsTimeoutMs</code> is hit
- */
- public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
- ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
- final int zen = mZenMode;
- if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
- if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm
- if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
- if (!mConfig.allowCalls) return false; // no calls get through
- if (validator != null) {
- final float contactAffinity = validator.getContactAffinity(userHandle, extras,
- contactsTimeoutMs, timeoutAffinity);
- return audienceMatches(contactAffinity);
- }
- }
- return true;
- }
-
- @Override
- public String toString() {
- return TAG;
- }
-
- private boolean audienceMatches(float contactAffinity) {
- switch (mConfig.allowFrom) {
- case ZenModeConfig.SOURCE_ANYONE:
- return true;
- case ZenModeConfig.SOURCE_CONTACT:
- return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
- case ZenModeConfig.SOURCE_STAR:
- return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
+ private static int getZenModeListenerInterruptionFilter(int zen) {
+ switch (zen) {
+ case Global.ZEN_MODE_OFF:
+ return NotificationListenerService.INTERRUPTION_FILTER_ALL;
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
+ case Global.ZEN_MODE_ALARMS:
+ return NotificationListenerService.INTERRUPTION_FILTER_ALARMS;
+ case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ return NotificationListenerService.INTERRUPTION_FILTER_NONE;
default:
- Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom);
- return true;
+ return 0;
}
}
- private class SettingsObserver extends ContentObserver {
+ private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter,
+ int defValue) {
+ switch (listenerInterruptionFilter) {
+ case NotificationListenerService.INTERRUPTION_FILTER_ALL:
+ return Global.ZEN_MODE_OFF;
+ case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY:
+ return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ case NotificationListenerService.INTERRUPTION_FILTER_ALARMS:
+ return Global.ZEN_MODE_ALARMS;
+ case NotificationListenerService.INTERRUPTION_FILTER_NONE:
+ return Global.ZEN_MODE_NO_INTERRUPTIONS;
+ default:
+ return defValue;
+ }
+ }
+
+ private ZenModeConfig readDefaultConfig(Resources resources) {
+ XmlResourceParser parser = null;
+ try {
+ parser = resources.getXml(R.xml.default_zen_mode_config);
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration);
+ if (config != null) return config;
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Error reading default zen mode config from resource", e);
+ } finally {
+ IoUtils.closeQuietly(parser);
+ }
+ return new ZenModeConfig();
+ }
+
+ private void appendDefaultScheduleRules(ZenModeConfig config) {
+ if (config == null) return;
+
+ final ScheduleInfo weeknights = new ScheduleInfo();
+ weeknights.days = ZenModeConfig.WEEKNIGHT_DAYS;
+ weeknights.startHour = 22;
+ weeknights.endHour = 7;
+ final ZenRule rule1 = new ZenRule();
+ rule1.enabled = false;
+ rule1.name = mContext.getResources()
+ .getString(R.string.zen_mode_default_weeknights_name);
+ rule1.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
+ rule1.zenMode = Global.ZEN_MODE_ALARMS;
+ config.automaticRules.put(config.newRuleId(), rule1);
+
+ final ScheduleInfo weekends = new ScheduleInfo();
+ weekends.days = ZenModeConfig.WEEKEND_DAYS;
+ weekends.startHour = 23;
+ weekends.startMinute = 30;
+ weekends.endHour = 10;
+ final ZenRule rule2 = new ZenRule();
+ rule2.enabled = false;
+ rule2.name = mContext.getResources()
+ .getString(R.string.zen_mode_default_weekends_name);
+ rule2.conditionId = ZenModeConfig.toScheduleConditionId(weekends);
+ rule2.zenMode = Global.ZEN_MODE_ALARMS;
+ config.automaticRules.put(config.newRuleId(), rule2);
+ }
+
+ private static int zenSeverity(int zen) {
+ switch (zen) {
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return 1;
+ case Global.ZEN_MODE_ALARMS: return 2;
+ case Global.ZEN_MODE_NO_INTERRUPTIONS: return 3;
+ default: return 0;
+ }
+ }
+
+ private final ZenModeConfig.Migration mConfigMigration = new ZenModeConfig.Migration() {
+ @Override
+ public ZenModeConfig migrate(ZenModeConfig.XmlV1 v1) {
+ if (v1 == null) return null;
+ final ZenModeConfig rt = new ZenModeConfig();
+ rt.allowCalls = v1.allowCalls;
+ rt.allowEvents = v1.allowEvents;
+ rt.allowFrom = v1.allowFrom;
+ rt.allowMessages = v1.allowMessages;
+ rt.allowReminders = v1.allowReminders;
+ // don't migrate current exit condition
+ final int[] days = ZenModeConfig.XmlV1.tryParseDays(v1.sleepMode);
+ if (days != null && days.length > 0) {
+ Log.i(TAG, "Migrating existing V1 downtime to single schedule");
+ final ScheduleInfo schedule = new ScheduleInfo();
+ schedule.days = days;
+ schedule.startHour = v1.sleepStartHour;
+ schedule.startMinute = v1.sleepStartMinute;
+ schedule.endHour = v1.sleepEndHour;
+ schedule.endMinute = v1.sleepEndMinute;
+ final ZenRule rule = new ZenRule();
+ rule.enabled = true;
+ rule.name = mContext.getResources()
+ .getString(R.string.zen_mode_downtime_feature_name);
+ rule.conditionId = ZenModeConfig.toScheduleConditionId(schedule);
+ rule.zenMode = v1.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS
+ : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ rt.automaticRules.put(rt.newRuleId(), rule);
+ } else {
+ Log.i(TAG, "No existing V1 downtime found, generating default schedules");
+ appendDefaultScheduleRules(rt);
+ }
+ return rt;
+ }
+ };
+
+ private final class RingerModeDelegate implements AudioManagerInternal.RingerModeDelegate {
+ @Override
+ public String toString() {
+ return TAG;
+ }
+
+ @Override
+ public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
+ int ringerModeExternal, VolumePolicy policy) {
+ final boolean isChange = ringerModeOld != ringerModeNew;
+
+ int ringerModeExternalOut = ringerModeNew;
+
+ int newZen = -1;
+ switch (ringerModeNew) {
+ case AudioManager.RINGER_MODE_SILENT:
+ if (isChange && policy.doNotDisturbWhenSilent) {
+ if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS
+ && mZenMode != Global.ZEN_MODE_ALARMS) {
+ newZen = Global.ZEN_MODE_NO_INTERRUPTIONS;
+ }
+ }
+ break;
+ case AudioManager.RINGER_MODE_VIBRATE:
+ case AudioManager.RINGER_MODE_NORMAL:
+ if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT
+ && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
+ || mZenMode == Global.ZEN_MODE_ALARMS)) {
+ newZen = Global.ZEN_MODE_OFF;
+ } else if (mZenMode != Global.ZEN_MODE_OFF) {
+ ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
+ }
+ break;
+ }
+ if (newZen != -1) {
+ setManualZenMode(newZen, null, "ringerModeInternal", false /*setRingerMode*/);
+ }
+
+ if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
+ ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
+ ringerModeExternal, ringerModeExternalOut);
+ }
+ return ringerModeExternalOut;
+ }
+
+ @Override
+ public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
+ int ringerModeInternal, VolumePolicy policy) {
+ int ringerModeInternalOut = ringerModeNew;
+ final boolean isChange = ringerModeOld != ringerModeNew;
+ final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
+
+ int newZen = -1;
+ switch (ringerModeNew) {
+ case AudioManager.RINGER_MODE_SILENT:
+ if (isChange) {
+ if (mZenMode == Global.ZEN_MODE_OFF) {
+ newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ }
+ ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE
+ : AudioManager.RINGER_MODE_NORMAL;
+ } else {
+ ringerModeInternalOut = ringerModeInternal;
+ }
+ break;
+ case AudioManager.RINGER_MODE_VIBRATE:
+ case AudioManager.RINGER_MODE_NORMAL:
+ if (mZenMode != Global.ZEN_MODE_OFF) {
+ newZen = Global.ZEN_MODE_OFF;
+ }
+ break;
+ }
+ if (newZen != -1) {
+ setManualZenMode(newZen, null, "ringerModeExternal", false /*setRingerMode*/);
+ }
+
+ ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller,
+ ringerModeInternal, ringerModeInternalOut);
+ return ringerModeInternalOut;
+ }
+ }
+
+ private final class SettingsObserver extends ContentObserver {
private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE);
public SettingsObserver(Handler handler) {
@@ -546,12 +585,15 @@
public void update(Uri uri) {
if (ZEN_MODE.equals(uri)) {
- readZenModeFromSetting();
+ if (mZenMode != getZenModeSetting()) {
+ if (DEBUG) Log.d(TAG, "Fixing zen mode setting");
+ setZenModeSetting(mZenMode);
+ }
}
}
}
- private class H extends Handler {
+ private final class H extends Handler {
private static final int MSG_DISPATCH = 1;
private H(Looper looper) {
@@ -577,4 +619,5 @@
void onConfigChanged() {}
void onZenModeChanged() {}
}
+
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index b4a44a6..ce31f98 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -20,7 +20,9 @@
import android.content.Context;
import android.content.pm.PackageStats;
import android.os.Build;
+import android.text.TextUtils;
import android.util.Slog;
+
import dalvik.system.VMRuntime;
import com.android.internal.os.InstallerConnection;
@@ -42,9 +44,24 @@
ping();
}
+ private static String escapeNull(String arg) {
+ if (TextUtils.isEmpty(arg)) {
+ return "!";
+ } else {
+ return arg;
+ }
+ }
+
+ @Deprecated
public int install(String name, int uid, int gid, String seinfo) {
+ return install(null, name, uid, gid, seinfo);
+ }
+
+ public int install(String uuid, String name, int uid, int gid, String seinfo) {
StringBuilder builder = new StringBuilder("install");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
@@ -55,43 +72,25 @@
return mInstaller.execute(builder.toString());
}
- public int patchoat(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet) {
+ public int dexopt(String apkPath, int uid, boolean isPublic,
+ String instructionSet, int dexoptNeeded) {
if (!isValidInstructionSet(instructionSet)) {
Slog.e(TAG, "Invalid instruction set: " + instructionSet);
return -1;
}
- return mInstaller.patchoat(apkPath, uid, isPublic, pkgName, instructionSet);
- }
-
- public int patchoat(String apkPath, int uid, boolean isPublic, String instructionSet) {
- if (!isValidInstructionSet(instructionSet)) {
- Slog.e(TAG, "Invalid instruction set: " + instructionSet);
- return -1;
- }
-
- return mInstaller.patchoat(apkPath, uid, isPublic, instructionSet);
- }
-
- public int dexopt(String apkPath, int uid, boolean isPublic, String instructionSet) {
- if (!isValidInstructionSet(instructionSet)) {
- Slog.e(TAG, "Invalid instruction set: " + instructionSet);
- return -1;
- }
-
- return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet);
+ return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet, dexoptNeeded);
}
public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName,
- String instructionSet, boolean vmSafeMode, boolean debuggable,
- @Nullable String outputPath) {
+ String instructionSet, int dexoptNeeded, boolean vmSafeMode,
+ boolean debuggable, @Nullable String outputPath) {
if (!isValidInstructionSet(instructionSet)) {
Slog.e(TAG, "Invalid instruction set: " + instructionSet);
return -1;
}
-
- return mInstaller.dexopt(apkPath, uid, isPublic, pkgName, instructionSet, vmSafeMode,
+ return mInstaller.dexopt(apkPath, uid, isPublic, pkgName,
+ instructionSet, dexoptNeeded, vmSafeMode,
debuggable, outputPath);
}
@@ -146,9 +145,16 @@
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int remove(String name, int userId) {
+ return remove(null, name, userId);
+ }
+
+ public int remove(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("remove");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
@@ -164,9 +170,16 @@
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int fixUid(String name, int uid, int gid) {
+ return fixUid(null, name, uid, gid);
+ }
+
+ public int fixUid(String uuid, String name, int uid, int gid) {
StringBuilder builder = new StringBuilder("fixuid");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
@@ -175,27 +188,48 @@
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int deleteCacheFiles(String name, int userId) {
+ return deleteCacheFiles(null, name, userId);
+ }
+
+ public int deleteCacheFiles(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("rmcache");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int deleteCodeCacheFiles(String name, int userId) {
+ return deleteCodeCacheFiles(null, name, userId);
+ }
+
+ public int deleteCodeCacheFiles(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("rmcodecache");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int createUserData(String name, int uid, int userId, String seinfo) {
+ return createUserData(null, name, uid, userId, seinfo);
+ }
+
+ public int createUserData(String uuid, String name, int uid, int userId, String seinfo) {
StringBuilder builder = new StringBuilder("mkuserdata");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(uid);
@@ -213,16 +247,30 @@
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int removeUserDataDirs(int userId) {
+ return removeUserDataDirs(null, userId);
+ }
+
+ public int removeUserDataDirs(String uuid, int userId) {
StringBuilder builder = new StringBuilder("rmuser");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(userId);
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int clearUserData(String name, int userId) {
+ return clearUserData(null, name, userId);
+ }
+
+ public int clearUserData(String uuid, String name, int userId) {
StringBuilder builder = new StringBuilder("rmuserdata");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(name);
builder.append(' ');
builder.append(userId);
@@ -249,15 +297,30 @@
}
}
+ @Deprecated
public int freeCache(long freeStorageSize) {
+ return freeCache(null, freeStorageSize);
+ }
+
+ public int freeCache(String uuid, long freeStorageSize) {
StringBuilder builder = new StringBuilder("freecache");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(String.valueOf(freeStorageSize));
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public int getSizeInfo(String pkgName, int persona, String apkPath, String libDirPath,
String fwdLockApkPath, String asecPath, String[] instructionSets, PackageStats pStats) {
+ return getSizeInfo(null, pkgName, persona, apkPath, libDirPath, fwdLockApkPath, asecPath,
+ instructionSets, pStats);
+ }
+
+ public int getSizeInfo(String uuid, String pkgName, int persona, String apkPath,
+ String libDirPath, String fwdLockApkPath, String asecPath, String[] instructionSets,
+ PackageStats pStats) {
for (String instructionSet : instructionSets) {
if (!isValidInstructionSet(instructionSet)) {
Slog.e(TAG, "Invalid instruction set: " + instructionSet);
@@ -267,6 +330,8 @@
StringBuilder builder = new StringBuilder("getsize");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(pkgName);
builder.append(' ');
builder.append(persona);
@@ -306,6 +371,11 @@
return mInstaller.execute("movefiles");
}
+ @Deprecated
+ public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath32, int userId) {
+ return linkNativeLibraryDirectory(null, dataPath, nativeLibPath32, userId);
+ }
+
/**
* Links the 32 bit native library directory in an application's data directory to the
* real location for backward compatibility. Note that no such symlink is created for
@@ -313,7 +383,8 @@
*
* @return -1 on error
*/
- public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath32, int userId) {
+ public int linkNativeLibraryDirectory(String uuid, String dataPath, String nativeLibPath32,
+ int userId) {
if (dataPath == null) {
Slog.e(TAG, "linkNativeLibraryDirectory dataPath is null");
return -1;
@@ -322,7 +393,10 @@
return -1;
}
- StringBuilder builder = new StringBuilder("linklib ");
+ StringBuilder builder = new StringBuilder("linklib");
+ builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(dataPath);
builder.append(' ');
builder.append(nativeLibPath32);
@@ -332,9 +406,16 @@
return mInstaller.execute(builder.toString());
}
+ @Deprecated
public boolean restoreconData(String pkgName, String seinfo, int uid) {
+ return restoreconData(null, pkgName, seinfo, uid);
+ }
+
+ public boolean restoreconData(String uuid, String pkgName, String seinfo, int uid) {
StringBuilder builder = new StringBuilder("restorecondata");
builder.append(' ');
+ builder.append(escapeNull(uuid));
+ builder.append(' ');
builder.append(pkgName);
builder.append(' ');
builder.append(seinfo != null ? seinfo : "!");
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 680ec4b..4c36fa6 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -113,64 +113,48 @@
for (String path : paths) {
try {
- // This will return DEXOPT_NEEDED if we either cannot find any odex file for this
- // package or the one we find does not match the image checksum (i.e. it was
- // compiled against an old image). It will return PATCHOAT_NEEDED if we can find a
- // odex file and it matches the checksum of the image but not its base address,
- // meaning we need to move it.
- final byte isDexOptNeeded = DexFile.isDexOptNeededInternal(path,
- pkg.packageName, dexCodeInstructionSet, defer);
- if (forceDex || (!defer && isDexOptNeeded == DexFile.DEXOPT_NEEDED)) {
- File oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
- Log.i(TAG, "Running dexopt on: " + path + " pkg="
+ final int dexoptNeeded;
+ if (forceDex) {
+ dexoptNeeded = DexFile.DEX2OAT_NEEDED;
+ } else {
+ dexoptNeeded = DexFile.getDexOptNeeded(path,
+ pkg.packageName, dexCodeInstructionSet, defer);
+ }
+
+ if (!forceDex && defer && dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ // We're deciding to defer a needed dexopt. Don't bother dexopting for other
+ // paths and instruction sets. We'll deal with them all together when we process
+ // our list of deferred dexopts.
+ addPackageForDeferredDexopt(pkg);
+ return DEX_OPT_DEFERRED;
+ }
+
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ final String dexoptType;
+ String oatDir = null;
+ if (dexoptNeeded == DexFile.DEX2OAT_NEEDED) {
+ dexoptType = "dex2oat";
+ oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
+ } else if (dexoptNeeded == DexFile.PATCHOAT_NEEDED) {
+ dexoptType = "patchoat";
+ } else if (dexoptNeeded == DexFile.SELF_PATCHOAT_NEEDED) {
+ dexoptType = "self patchoat";
+ } else {
+ throw new IllegalStateException("Invalid dexopt needed: " + dexoptNeeded);
+ }
+ Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
+ pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
+ " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
+ " oatDir = " + oatDir);
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
-
- if (oatDir != null) {
- int ret = mPackageManagerService.mInstaller.dexopt(
- path, sharedGid, !pkg.isForwardLocked(), pkg.packageName,
- dexCodeInstructionSet, vmSafeMode, debuggable,
- oatDir.getAbsolutePath());
- if (ret < 0) {
- return DEX_OPT_FAILED;
- }
- } else {
- final int ret = mPackageManagerService.mInstaller
- .dexopt(path, sharedGid,
- !pkg.isForwardLocked(), pkg.packageName,
- dexCodeInstructionSet,
- vmSafeMode, debuggable, null);
- if (ret < 0) {
- return DEX_OPT_FAILED;
- }
- }
-
- performedDexOpt = true;
- } else if (!defer && isDexOptNeeded == DexFile.PATCHOAT_NEEDED) {
- Log.i(TAG, "Running patchoat on: " + pkg.applicationInfo.packageName);
- final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
- final int ret = mPackageManagerService.mInstaller.patchoat(path, sharedGid,
- !pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet);
-
+ final int ret = mPackageManagerService.mInstaller.dexopt(path, sharedGid,
+ !pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet,
+ dexoptNeeded, vmSafeMode, debuggable, oatDir);
if (ret < 0) {
- // Don't bother running patchoat again if we failed, it will probably
- // just result in an error again. Also, don't bother dexopting for other
- // paths & ISAs.
return DEX_OPT_FAILED;
}
-
performedDexOpt = true;
}
-
- // We're deciding to defer a needed dexopt. Don't bother dexopting for other
- // paths and instruction sets. We'll deal with them all together when we process
- // our list of deferred dexopts.
- if (defer && isDexOptNeeded != DexFile.UP_TO_DATE) {
- addPackageForDeferredDexopt(pkg);
- return DEX_OPT_DEFERRED;
- }
} catch (FileNotFoundException e) {
Slog.w(TAG, "Apk not found for dexopt: " + path);
return DEX_OPT_FAILED;
@@ -187,7 +171,7 @@
}
// At this point we haven't failed dexopt and we haven't deferred dexopt. We must
- // either have either succeeded dexopt, or have had isDexOptNeededInternal tell us
+ // either have either succeeded dexopt, or have had getDexOptNeeded tell us
// it isn't required. We therefore mark that this package doesn't need dexopt unless
// it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
// it.
@@ -209,10 +193,11 @@
* <li>Package location is not a directory, i.e. monolithic install.</li>
* </ul>
*
- * @return oat directory or null, if oat directory cannot be created.
+ * @return Absolute path to the oat directory or null, if oat directory
+ * cannot be created.
*/
@Nullable
- private File createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet)
+ private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet)
throws IOException {
if (pkg.isSystemApp() && !pkg.isUpdatedSystemApp()) {
return null;
@@ -222,7 +207,7 @@
File oatDir = getOatDir(codePath);
mPackageManagerService.mInstaller.createOatDir(oatDir.getAbsolutePath(),
dexInstructionSet);
- return oatDir;
+ return oatDir.getAbsolutePath();
}
return null;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 591dbee..89fa320 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -30,6 +30,7 @@
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -528,6 +529,15 @@
params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
}
+ // Only system components can circumvent runtime permissions when installing.
+ if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
+ && mContext.checkCallingOrSelfPermission(Manifest.permission
+ .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
+ throw new SecurityException("You need the "
+ + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
+ + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
+ }
+
// Defensively resize giant app icons
if (params.appIcon != null) {
final ActivityManager am = (ActivityManager) mContext.getSystemService(
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 80a4351..11e1ccf 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -560,7 +560,6 @@
mIntentFilterVerificationStates.get(verificationId);
String packageName = ivs.getPackageName();
- boolean modified = false;
ArrayList<PackageParser.ActivityIntentInfo> filters = ivs.getFilters();
final int filterCount = filters.size();
@@ -571,9 +570,8 @@
}
ArrayList<String> domainsList = new ArrayList<>(domainsSet);
synchronized (mPackages) {
- modified = mSettings.createIntentFilterVerificationIfNeededLPw(
- packageName, domainsList);
- if (modified) {
+ if (mSettings.createIntentFilterVerificationIfNeededLPw(
+ packageName, domainsList) != null) {
scheduleWriteSettingsLocked();
}
}
@@ -634,7 +632,7 @@
+ verificationId + " packageName:" + packageName);
return;
}
- Slog.d(TAG, "Updating IntentFilterVerificationInfo for verificationId: "
+ Slog.d(TAG, "Updating IntentFilterVerificationInfo for verificationId:"
+ verificationId);
synchronized (mPackages) {
@@ -698,8 +696,7 @@
ivs = createDomainVerificationState(verifierId, userId, verificationId,
packageName);
}
- ArrayList<String> hosts = filter.getHostsList();
- if (!hasValidHosts(hosts)) {
+ if (!hasValidDomains(filter)) {
return false;
}
ivs.addFilter(filter);
@@ -719,17 +716,35 @@
}
}
- private static boolean hasValidHosts(ArrayList<String> hosts) {
- if (hosts.size() == 0) {
- Slog.d(TAG, "IntentFilter does not contain any data hosts");
+ private static boolean hasValidDomains(ActivityIntentInfo filter) {
+ return hasValidDomains(filter, true);
+ }
+
+ private static boolean hasValidDomains(ActivityIntentInfo filter, boolean logging) {
+ boolean hasHTTPorHTTPS = filter.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
+ filter.hasDataScheme(IntentFilter.SCHEME_HTTPS);
+ if (!hasHTTPorHTTPS) {
+ if (logging) {
+ Slog.d(TAG, "IntentFilter does not contain any HTTP or HTTPS data scheme");
+ }
return false;
}
+ ArrayList<String> hosts = filter.getHostsList();
+ if (hosts.size() == 0) {
+ if (logging) {
+ Slog.d(TAG, "IntentFilter does not contain any data hosts");
+ }
+ // We still return true as this is the case of any Browser
+ return true;
+ }
String hostEndBase = null;
for (String host : hosts) {
String[] hostParts = host.split("\\.");
// Should be at minimum a host like "example.com"
if (hostParts.length < 2) {
- Slog.d(TAG, "IntentFilter does not contain a valid data host name: " + host);
+ if (logging) {
+ Slog.d(TAG, "IntentFilter does not contain a valid data host name: " + host);
+ }
return false;
}
// Verify that we have the same ending domain
@@ -739,7 +754,9 @@
hostEndBase = hostEnd;
}
if (!hostEnd.equalsIgnoreCase(hostEndBase)) {
- Slog.d(TAG, "IntentFilter does not contain the same data domains");
+ if (logging) {
+ Slog.d(TAG, "IntentFilter does not contain the same data domains");
+ }
return false;
}
}
@@ -1830,18 +1847,10 @@
}
try {
- byte dexoptRequired = DexFile.isDexOptNeededInternal(lib, null,
- dexCodeInstructionSet,
- false);
- if (dexoptRequired != DexFile.UP_TO_DATE) {
+ int dexoptNeeded = DexFile.getDexOptNeeded(lib, null, dexCodeInstructionSet, false);
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
alreadyDexOpted.add(lib);
-
- // The list of "shared libraries" we have at this point is
- if (dexoptRequired == DexFile.DEXOPT_NEEDED) {
- mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
- } else {
- mInstaller.patchoat(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
- }
+ mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Library not found: " + lib);
@@ -1887,13 +1896,9 @@
continue;
}
try {
- byte dexoptRequired = DexFile.isDexOptNeededInternal(path, null,
- dexCodeInstructionSet,
- false);
- if (dexoptRequired == DexFile.DEXOPT_NEEDED) {
- mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
- } else if (dexoptRequired == DexFile.PATCHOAT_NEEDED) {
- mInstaller.patchoat(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
+ int dexoptNeeded = DexFile.getDexOptNeeded(path, null, dexCodeInstructionSet, false);
+ if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
+ mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
}
} catch (FileNotFoundException e) {
Slog.w(TAG, "Jar not found: " + path);
@@ -2176,6 +2181,8 @@
mIntentFilterVerifier = new IntentVerifierProxy(mContext,
mIntentFilterVerifierComponent);
+ primeDomainVerificationsLPw(false);
+
} // synchronized (mPackages)
} // synchronized (mInstallLock)
@@ -2272,6 +2279,50 @@
return verifierComponentName;
}
+ private void primeDomainVerificationsLPw(boolean logging) {
+ Slog.d(TAG, "Start priming domain verification");
+ boolean updated = false;
+ ArrayList<String> allHosts = new ArrayList<>();
+ for (PackageParser.Package pkg : mPackages.values()) {
+ final String packageName = pkg.packageName;
+ if (!hasDomainURLs(pkg)) {
+ if (logging) {
+ Slog.d(TAG, "No priming domain verifications for " +
+ "package with no domain URLs: " + packageName);
+ }
+ continue;
+ }
+ for (PackageParser.Activity a : pkg.activities) {
+ for (ActivityIntentInfo filter : a.intents) {
+ if (hasValidDomains(filter, false)) {
+ allHosts.addAll(filter.getHostsList());
+ }
+ }
+ }
+ if (allHosts.size() > 0) {
+ allHosts.add("*");
+ }
+ IntentFilterVerificationInfo ivi =
+ mSettings.createIntentFilterVerificationIfNeededLPw(packageName, allHosts);
+ if (ivi != null) {
+ // We will always log this
+ Slog.d(TAG, "Priming domain verifications for package: " + packageName);
+ ivi.setStatus(INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS);
+ updated = true;
+ }
+ else {
+ if (logging) {
+ Slog.d(TAG, "No priming domain verifications for package: " + packageName);
+ }
+ }
+ allHosts.clear();
+ }
+ if (updated) {
+ scheduleWriteSettingsLocked();
+ }
+ Slog.d(TAG, "End priming domain verification");
+ }
+
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -3904,10 +3955,9 @@
Collections.sort(result, mResolvePrioritySorter);
}
result = filterIfNotPrimaryUser(result, userId);
- if (result.size() > 1) {
+ if (result.size() > 1 && hasWebURI(intent)) {
return filterCandidatesWithDomainPreferedActivitiesLPr(result);
}
-
return result;
}
final PackageParser.Package pkg = mPackages.get(pkgName);
@@ -3939,14 +3989,30 @@
return resolveInfos;
}
+ private static boolean hasWebURI(Intent intent) {
+ if (intent.getData() == null) {
+ return false;
+ }
+ final String scheme = intent.getScheme();
+ if (TextUtils.isEmpty(scheme)) {
+ return false;
+ }
+ return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS);
+ }
+
private List<ResolveInfo> filterCandidatesWithDomainPreferedActivitiesLPr(
List<ResolveInfo> candidates) {
if (DEBUG_PREFERRED) {
Slog.v("TAG", "Filtering results with prefered activities. Candidates count: " +
candidates.size());
}
+
final int userId = UserHandle.getCallingUserId();
ArrayList<ResolveInfo> result = new ArrayList<ResolveInfo>();
+ ArrayList<ResolveInfo> undefinedList = new ArrayList<ResolveInfo>();
+ ArrayList<ResolveInfo> neverList = new ArrayList<ResolveInfo>();
+ ArrayList<ResolveInfo> matchAllList = new ArrayList<ResolveInfo>();
+
synchronized (mPackages) {
final int count = candidates.size();
// First, try to use the domain prefered App
@@ -3959,13 +4025,31 @@
int status = getDomainVerificationStatusLPr(ps, userId);
if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
result.add(info);
+ } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+ neverList.add(info);
+ } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+ undefinedList.add(info);
+ }
+ // Add to the special match all list (Browser use case)
+ if (info.handleAllWebDataURI) {
+ matchAllList.add(info);
}
}
}
- // There is not much we can do, add all candidates
+ // If there is nothing selected, add all candidates and remove the ones that the User
+ // has explicitely put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state and
+ // also remove any Browser Apps ones.
+ // If there is still none after this pass, add all undefined one and Browser Apps and
+ // let the User decide with the Disambiguation dialog if there are several ones.
if (result.size() == 0) {
result.addAll(candidates);
}
+ result.removeAll(neverList);
+ result.removeAll(matchAllList);
+ if (result.size() == 0) {
+ result.addAll(undefinedList);
+ result.addAll(matchAllList);
+ }
}
if (DEBUG_PREFERRED) {
Slog.v("TAG", "Filtered results with prefered activities. New candidates count: " +
@@ -7843,7 +7927,7 @@
res.filter = info;
}
if (info != null) {
- res.filterNeedsVerification = info.needsVerification();
+ res.handleAllWebDataURI = info.handleAllWebDataURI();
}
res.priority = info.getPriority();
res.preferredOrder = activity.owner.mPreferredOrder;
@@ -8528,6 +8612,15 @@
user = new UserHandle(userId);
}
+ // Only system components can circumvent runtime permissions when installing.
+ if ((installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
+ && mContext.checkCallingOrSelfPermission(Manifest.permission
+ .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
+ throw new SecurityException("You need the "
+ + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
+ + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
+ }
+
verificationParams.setInstallerUid(callingUid);
final File originFile = new File(originPath);
@@ -8685,7 +8778,6 @@
long callingId = Binder.clearCallingIdentity();
try {
boolean sendAdded = false;
- Bundle extras = new Bundle(1);
// writer
synchronized (mPackages) {
@@ -11215,6 +11307,12 @@
return;
}
+ final boolean hasDomainURLs = hasDomainURLs(pkg);
+ if (!hasDomainURLs) {
+ Slog.d(TAG, "No domain URLs, so no need to verify any IntentFilter!");
+ return;
+ }
+
Slog.d(TAG, "Checking for userId:" + userId + " if any IntentFilter from the " + size
+ " Activities needs verification ...");
@@ -11222,22 +11320,25 @@
int count = 0;
final String packageName = pkg.packageName;
ArrayList<String> allHosts = new ArrayList<>();
+
synchronized (mPackages) {
for (PackageParser.Activity a : pkg.activities) {
for (ActivityIntentInfo filter : a.intents) {
- boolean needFilterVerification = filter.needsVerification() &&
- !filter.isVerified();
- if (needFilterVerification && needNetworkVerificationLPr(filter)) {
+ boolean needsFilterVerification = filter.needsVerification();
+ if (needsFilterVerification && needsNetworkVerificationLPr(filter)) {
Slog.d(TAG, "Verification needed for IntentFilter:" + filter.toString());
mIntentFilterVerifier.addOneIntentFilterVerification(
verifierUid, userId, verificationId, filter, packageName);
count++;
- } else {
- Slog.d(TAG, "No verification needed for IntentFilter:" + filter.toString());
- ArrayList<String> list = filter.getHostsList();
- if (hasValidHosts(list)) {
- allHosts.addAll(list);
+ } else if (!needsFilterVerification) {
+ Slog.d(TAG, "No verification needed for IntentFilter:"
+ + filter.toString());
+ if (hasValidDomains(filter)) {
+ allHosts.addAll(filter.getHostsList());
}
+ } else {
+ Slog.d(TAG, "Verification already done for IntentFilter:"
+ + filter.toString());
}
}
}
@@ -11249,15 +11350,14 @@
+ (count > 1 ? "s" : "") + " for userId:" + userId + "!");
} else {
Slog.d(TAG, "No need to start any IntentFilter verification!");
- if (allHosts.size() > 0 && hasDomainURLs(pkg) &&
- mSettings.createIntentFilterVerificationIfNeededLPw(
- packageName, allHosts)) {
+ if (allHosts.size() > 0 && mSettings.createIntentFilterVerificationIfNeededLPw(
+ packageName, allHosts) != null) {
scheduleWriteSettingsLocked();
}
}
}
- private boolean needNetworkVerificationLPr(ActivityIntentInfo filter) {
+ private boolean needsNetworkVerificationLPr(ActivityIntentInfo filter) {
final ComponentName cn = filter.activity.getComponentName();
final String packageName = cn.getPackageName();
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 1ff6fb8..c75a1d3 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -34,6 +34,7 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -52,14 +53,15 @@
*/
public final class SELinuxMMAC {
- private static final String TAG = "SELinuxMMAC";
+ static final String TAG = "SELinuxMMAC";
private static final boolean DEBUG_POLICY = false;
private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false;
+ private static final boolean DEBUG_POLICY_ORDER = DEBUG_POLICY || false;
// All policy stanzas read from mac_permissions.xml. This is also the lock
// to synchronize access during policy load and access attempts.
- private static final List<Policy> sPolicies = new ArrayList<Policy>();
+ private static List<Policy> sPolicies = new ArrayList<>();
// Data policy override version file.
private static final String DATA_VERSION_FILE =
@@ -112,17 +114,9 @@
* were loaded successfully; no partial loading is possible.
*/
public static boolean readInstallPolicy() {
- // Temp structure to hold the rules while we parse the xml file. We add
- // all the rules once we know there's no problems.
+ // Temp structure to hold the rules while we parse the xml file
List<Policy> policies = new ArrayList<>();
- // A separate structure to hold the default stanza. We need to add this to
- // the end of the policies list structure.
- Policy defaultPolicy = null;
-
- // Track sets of known policy certs so we can enforce rules across stanzas.
- Set<Set<Signature>> knownCerts = new HashSet<>();
-
FileReader policyFile = null;
XmlPullParser parser = Xml.newPullParser();
try {
@@ -138,31 +132,15 @@
continue;
}
- String tagName = parser.getName();
- if ("signer".equals(tagName)) {
- Policy signerPolicy = readSignerOrThrow(parser);
- // Return of a Policy instance ensures certain invariants have
- // passed, however, we still want to do some cross policy checking.
- // Thus, check that we haven't seen the certs in another stanza.
- Set<Signature> certs = signerPolicy.getSignatures();
- if (knownCerts.contains(certs)) {
- String msg = "Separate stanzas have identical certs";
- throw new IllegalStateException(msg);
- }
- knownCerts.add(certs);
- policies.add(signerPolicy);
- } else if ("default".equals(tagName)) {
- Policy defPolicy = readDefaultOrThrow(parser);
- // Return of a Policy instance ensures certain invariants have
- // passed, however, we still want to do some cross policy checking.
- // Thus, check that we haven't already seen a default stanza.
- if (defaultPolicy != null) {
- String msg = "Multiple default stanzas identified";
- throw new IllegalStateException(msg);
- }
- defaultPolicy = defPolicy;
- } else {
- skip(parser);
+ switch (parser.getName()) {
+ case "signer":
+ policies.add(readSignerOrThrow(parser));
+ break;
+ case "default":
+ policies.add(readDefaultOrThrow(parser));
+ break;
+ default:
+ skip(parser);
}
}
} catch (IllegalStateException | IllegalArgumentException |
@@ -182,15 +160,22 @@
IoUtils.closeQuietly(policyFile);
}
- // Add the default policy to the end if there is one. This will ensure that
- // the default stanza is consulted last when performing policy lookups.
- if (defaultPolicy != null) {
- policies.add(defaultPolicy);
+ // Now sort the policy stanzas
+ PolicyComparator policySort = new PolicyComparator();
+ Collections.sort(policies, policySort);
+ if (policySort.foundDuplicate()) {
+ Slog.w(TAG, "ERROR! Duplicate entries found parsing " + MAC_PERMISSIONS);
+ return false;
}
synchronized (sPolicies) {
- sPolicies.clear();
- sPolicies.addAll(policies);
+ sPolicies = policies;
+
+ if (DEBUG_POLICY_ORDER) {
+ for (Policy policy : sPolicies) {
+ Slog.d(TAG, "Policy: " + policy.toString());
+ }
+ }
}
return true;
@@ -494,9 +479,23 @@
* of invariants before being built and returned. Each instance can be guaranteed to
* hold one valid policy stanza as outlined in the external/sepolicy/mac_permissions.xml
* file.
- * </p>
+ * <p>
* The following is an example of how to use {@link Policy.PolicyBuilder} to create a
- * signer based Policy instance.
+ * signer based Policy instance with only inner package name refinements.
+ * </p>
+ * <pre>
+ * {@code
+ * Policy policy = new Policy.PolicyBuilder()
+ * .addSignature("308204a8...")
+ * .addSignature("483538c8...")
+ * .addInnerPackageMapOrThrow("com.foo.", "bar")
+ * .addInnerPackageMapOrThrow("com.foo.other", "bar")
+ * .build();
+ * }
+ * </pre>
+ * <p>
+ * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
+ * signer based Policy instance with only a global seinfo tag.
* </p>
* <pre>
* {@code
@@ -504,20 +503,18 @@
* .addSignature("308204a8...")
* .addSignature("483538c8...")
* .setGlobalSeinfoOrThrow("paltform")
- * .addInnerPackageMapOrThrow("com.foo.", "bar")
- * .addInnerPackageMapOrThrow("com.foo.other", "bar")
* .build();
* }
* </pre>
* <p>
- * An example of how to use {@link Policy.PolicyBuilder} to create a default based Policy
- * instance.
+ * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
+ * default based Policy instance.
* </p>
* <pre>
* {@code
* Policy policy = new Policy.PolicyBuilder()
* .setAsDefaultPolicy()
- * .setGlobalSeinfoOrThrow("defualt")
+ * .setGlobalSeinfoOrThrow("default")
* .build();
* }
* </pre>
@@ -548,6 +545,65 @@
}
/**
+ * Return whether this policy object represents a default stanza.
+ *
+ * @return A boolean indicating if this object represents a default policy stanza.
+ */
+ public boolean isDefaultStanza() {
+ return mDefaultStanza;
+ }
+
+ /**
+ * Return whether this policy object contains package name mapping refinements.
+ *
+ * @return A boolean indicating if this object has inner package name mappings.
+ */
+ public boolean hasInnerPackages() {
+ return !mPkgMap.isEmpty();
+ }
+
+ /**
+ * Return the mapping of all package name refinements.
+ *
+ * @return A Map object whose keys are the package names and whose values are
+ * the seinfo assignments.
+ */
+ public Map<String, String> getInnerPackages() {
+ return mPkgMap;
+ }
+
+ /**
+ * Return whether the policy object has a global seinfo tag attached.
+ *
+ * @return A boolean indicating if this stanza has a global seinfo tag.
+ */
+ public boolean hasGlobalSeinfo() {
+ return mSeinfo != null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (mDefaultStanza) {
+ sb.append("defaultStanza=true ");
+ }
+
+ for (Signature cert : mCerts) {
+ sb.append("cert=" + cert.toCharsString().substring(0, 11) + "... ");
+ }
+
+ if (mSeinfo != null) {
+ sb.append("seinfo=" + mSeinfo);
+ }
+
+ for (String name : mPkgMap.keySet()) {
+ sb.append(" " + name + "=" + mPkgMap.get(name));
+ }
+
+ return sb.toString();
+ }
+
+ /**
* <p>
* Determine the seinfo value to assign to an apk. The appropriate seinfo value
* is determined using the following steps:
@@ -620,7 +676,7 @@
}
/**
- * Sets this stanza as a defualt stanza. All policy stanzas are assumed to
+ * Sets this stanza as a default stanza. All policy stanzas are assumed to
* be signer stanzas unless this method is explicitly called. Default stanzas
* are treated differently with respect to allowable child tags, ordering and
* when and how policy decisions are enforced.
@@ -754,7 +810,7 @@
* <ul>
* <li> at least one cert must be found </li>
* <li> either a global seinfo value is present OR at least one
- * inner package mapping must be present. </li>
+ * inner package mapping must be present BUT not both. </li>
* </ul>
* </li>
* </ul>
@@ -783,9 +839,9 @@
String err = "Missing certs with signer tag. Expecting at least one.";
throw new IllegalStateException(err);
}
- if ((p.mSeinfo == null) && (p.mPkgMap.isEmpty())) {
- String err = "Missing seinfo OR package tags with signer tag. At " +
- "least one must be present.";
+ if (!(p.mSeinfo == null ^ p.mPkgMap.isEmpty())) {
+ String err = "Only seinfo tag XOR package tags are allowed within " +
+ "a signer stanza.";
throw new IllegalStateException(err);
}
}
@@ -794,3 +850,58 @@
}
}
}
+
+/**
+ * Comparision imposing an ordering on Policy objects. It is understood that Policy
+ * objects can only take one of three forms and ordered according to the following
+ * set of rules most specific to least.
+ * <ul>
+ * <li> signer stanzas with inner package mappings </li>
+ * <li> signer stanzas with global seinfo tags </li>
+ * <li> default stanza </li>
+ * </ul>
+ * This comparison also checks for duplicate entries on the input selectors. Any
+ * found duplicates will be flagged and can be checked with {@link #foundDuplicate}.
+ */
+
+final class PolicyComparator implements Comparator<Policy> {
+
+ private boolean duplicateFound = false;
+
+ public boolean foundDuplicate() {
+ return duplicateFound;
+ }
+
+ @Override
+ public int compare(Policy p1, Policy p2) {
+
+ // Give precedence to signature stanzas over default stanzas
+ if (p1.isDefaultStanza() != p2.isDefaultStanza()) {
+ return p1.isDefaultStanza() ? 1 : -1;
+ }
+
+ // Give precedence to stanzas with inner package mappings
+ if (p1.hasInnerPackages() != p2.hasInnerPackages()) {
+ return p1.hasInnerPackages() ? -1 : 1;
+ }
+
+ // Check for duplicate entries
+ if (p1.getSignatures().equals(p2.getSignatures())) {
+ // Checks if default stanza or a signer w/o inner package names
+ if (p1.hasGlobalSeinfo()) {
+ duplicateFound = true;
+ Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString());
+ }
+
+ // Look for common inner package name mappings
+ final Map<String, String> p1Packages = p1.getInnerPackages();
+ final Map<String, String> p2Packages = p2.getInnerPackages();
+ if (!Collections.disjoint(p1Packages.keySet(), p2Packages.keySet())) {
+ duplicateFound = true;
+ Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString());
+ }
+ }
+
+ return 0;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 2e2053d..bfcc3db 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -966,19 +966,19 @@
}
/* package protected */
- boolean createIntentFilterVerificationIfNeededLPw(String packageName,
+ IntentFilterVerificationInfo createIntentFilterVerificationIfNeededLPw(String packageName,
ArrayList<String> domains) {
PackageSetting ps = mPackages.get(packageName);
if (ps == null) {
Slog.w(PackageManagerService.TAG, "No package known for name: " + packageName);
- return false;
+ return null;
}
- if (ps.getIntentFilterVerificationInfo() == null) {
- IntentFilterVerificationInfo ivi = new IntentFilterVerificationInfo(packageName, domains);
+ IntentFilterVerificationInfo ivi = ps.getIntentFilterVerificationInfo();
+ if (ivi == null) {
+ ivi = new IntentFilterVerificationInfo(packageName, domains);
ps.setIntentFilterVerificationInfo(ivi);
- return true;
}
- return false;
+ return ivi;
}
int getIntentFilterVerificationStatusLPr(String packageName, int userId) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ed14569..936840a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -990,7 +990,7 @@
launchHomeFromHotKey();
break;
case SHORT_PRESS_POWER_GO_HOME:
- launchHomeFromHotKey();
+ launchHomeFromHotKey(true /* awakenFromDreams */, false /*respectKeyguard*/);
break;
}
}
@@ -1068,7 +1068,7 @@
PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0);
break;
case SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME:
- launchHomeFromHotKey(false /* awakenDreams */);
+ launchHomeFromHotKey(false /* awakenDreams */, true /*respectKeyguard*/);
mPowerManager.goToSleep(event.getEventTime(),
PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0);
break;
@@ -3059,50 +3059,56 @@
}
void launchHomeFromHotKey() {
- launchHomeFromHotKey(true /* awakenFromDreams */);
+ launchHomeFromHotKey(true /* awakenFromDreams */, true /*respectKeyguard*/);
}
/**
* A home key -> launch home action was detected. Take the appropriate action
* given the situation with the keyguard.
*/
- void launchHomeFromHotKey(final boolean awakenFromDreams) {
- if (isKeyguardShowingAndNotOccluded()) {
- // don't launch home if keyguard showing
- } else if (!mHideLockScreen && mKeyguardDelegate.isInputRestricted()) {
- // when in keyguard restricted mode, must first verify unlock
- // before launching home
- mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
- @Override
- public void onKeyguardExitResult(boolean success) {
- if (success) {
- try {
- ActivityManagerNative.getDefault().stopAppSwitches();
- } catch (RemoteException e) {
+ void launchHomeFromHotKey(final boolean awakenFromDreams, final boolean respectKeyguard) {
+ if (respectKeyguard) {
+ if (isKeyguardShowingAndNotOccluded()) {
+ // don't launch home if keyguard showing
+ return;
+ }
+
+ if (!mHideLockScreen && mKeyguardDelegate.isInputRestricted()) {
+ // when in keyguard restricted mode, must first verify unlock
+ // before launching home
+ mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
+ @Override
+ public void onKeyguardExitResult(boolean success) {
+ if (success) {
+ try {
+ ActivityManagerNative.getDefault().stopAppSwitches();
+ } catch (RemoteException e) {
+ }
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+ startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
}
- sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
- startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
}
- }
- });
+ });
+ return;
+ }
+ }
+
+ // no keyguard stuff to worry about, just launch home!
+ try {
+ ActivityManagerNative.getDefault().stopAppSwitches();
+ } catch (RemoteException e) {
+ }
+ if (mRecentsVisible) {
+ // Hide Recents and notify it to launch Home
+ if (awakenFromDreams) {
+ awakenDreams();
+ }
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+ hideRecentApps(false, true);
} else {
- // no keyguard stuff to worry about, just launch home!
- try {
- ActivityManagerNative.getDefault().stopAppSwitches();
- } catch (RemoteException e) {
- }
- if (mRecentsVisible) {
- // Hide Recents and notify it to launch Home
- if (awakenFromDreams) {
- awakenDreams();
- }
- sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
- hideRecentApps(false, true);
- } else {
- // Otherwise, just launch Home
- sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
- startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
- }
+ // Otherwise, just launch Home
+ sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+ startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
}
}
diff --git a/services/core/java/com/android/server/power/DeviceIdleController.java b/services/core/java/com/android/server/power/DeviceIdleController.java
index dd00446..a23a87b 100644
--- a/services/core/java/com/android/server/power/DeviceIdleController.java
+++ b/services/core/java/com/android/server/power/DeviceIdleController.java
@@ -377,7 +377,7 @@
}
}
- void scheduleAlarmLocked(long delay, boolean wakeup) {
+ void scheduleAlarmLocked(long delay, boolean idleUntil) {
if (mSigMotionSensor == null) {
// If there is no significant motion sensor on this device, then we won't schedule
// alarms, because we can't determine if the device is not moving. This effectively
@@ -386,8 +386,13 @@
return;
}
mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
- mAlarmManager.set(wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP
- : AlarmManager.ELAPSED_REALTIME, mNextAlarmTime, mAlarmIntent);
+ if (idleUntil) {
+ mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mNextAlarmTime, mAlarmIntent);
+ } else {
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mNextAlarmTime, mAlarmIntent);
+ }
}
private void dumpHelp(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 9b4b522..3262cc6 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -204,6 +204,14 @@
}
@Override
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ // The input list needs to be updated in any cases, regardless of whether
+ // it happened to the whole package or a specific component. Returning true so that
+ // the update can be handled in {@link #onSomePackagesChanged}.
+ return true;
+ }
+
+ @Override
public void onPackageRemoved(String packageName, int uid) {
synchronized (mLock) {
UserState userState = getUserStateLocked(getChangingUserId());
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7fab5a6..e790fb0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4701,7 +4701,8 @@
WindowState w = wtoken.allAppWindows.get(i);
if (w.mAppFreezing) {
w.mAppFreezing = false;
- if (w.mHasSurface && !w.mOrientationChanging) {
+ if (w.mHasSurface && !w.mOrientationChanging
+ && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
if (DEBUG_ORIENTATION) Slog.v(TAG, "set mOrientationChanging of " + w);
w.mOrientationChanging = true;
mInnerFields.mOrientationChangeComplete = false;
@@ -9021,7 +9022,7 @@
// If the screen is currently frozen or off, then keep
// it frozen/off until this window draws at its new
// orientation.
- if (!okToDisplay()) {
+ if (!okToDisplay() && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
if (DEBUG_ORIENTATION) Slog.v(TAG, "Changing surface while display frozen: " + w);
w.mOrientationChanging = true;
w.mLastFreezeDuration = 0;
diff --git a/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp b/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp
index 049e455..d5508bc 100644
--- a/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp
+++ b/services/core/jni/com_android_server_location_FlpHardwareProvider.cpp
@@ -297,6 +297,14 @@
getSourcesToUse
);
+ jmethodID getSmallestDisplacementMeters = env->GetMethodID(
+ batchOptionsClass,
+ "getSmallestDisplacementMeters",
+ "()F"
+ );
+ batchOptions.smallest_displacement_meters
+ = env->CallFloatMethod(batchOptionsObject, getSmallestDisplacementMeters);
+
jmethodID getFlags = env->GetMethodID(batchOptionsClass, "getFlags", "()I");
batchOptions.flags = env->CallIntMethod(batchOptionsObject, getFlags);
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index 1d2180e..c1c5c56 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -372,8 +372,8 @@
int numOutputPorts, String[] inputPortNames, String[] outputPortNames,
Bundle properties, int type) {
int uid = Binder.getCallingUid();
- if (type != MidiDeviceInfo.TYPE_VIRTUAL && uid != Process.SYSTEM_UID) {
- throw new SecurityException("only system can create non-virtual devices");
+ if (type == MidiDeviceInfo.TYPE_USB && uid != Process.SYSTEM_UID) {
+ throw new SecurityException("only system can create USB devices");
}
synchronized (mDevicesByInfo) {
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 7383478..7ea5aa7 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -39,6 +39,7 @@
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.anyLong;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
@@ -879,7 +880,7 @@
expectLastCall().anyTimes();
mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
- isA(PendingIntent.class), isA(WorkSource.class),
+ anyInt(), isA(PendingIntent.class), isA(WorkSource.class),
isA(AlarmManager.AlarmClockInfo.class));
expectLastCall().atLeastOnce();
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 2728af1..8f0c6c8 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -469,10 +469,12 @@
if (enabled && mPeripheralMidiDevice == null) {
Bundle properties = new Bundle();
Resources r = mContext.getResources();
+ properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString(
+ com.android.internal.R.string.usb_midi_peripheral_name));
properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString(
com.android.internal.R.string.usb_midi_peripheral_manufacturer_name));
properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString(
- com.android.internal.R.string.usb_midi_peripheral_model_name));
+ com.android.internal.R.string.usb_midi_peripheral_product_name));
properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device);
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index 6ece888..671cf01 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -45,7 +45,8 @@
private MidiDeviceServer mServer;
- private final MidiEventScheduler mEventScheduler;
+ // event schedulers for each output port
+ private final MidiEventScheduler[] mEventSchedulers;
private static final int BUFFER_SIZE = 512;
@@ -99,10 +100,11 @@
}
mOutputStreams = new FileOutputStream[outputCount];
+ mEventSchedulers = new MidiEventScheduler[outputCount];
for (int i = 0; i < outputCount; i++) {
mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
+ mEventSchedulers[i] = new MidiEventScheduler();
}
- mEventScheduler = new MidiEventScheduler(inputCount);
}
private boolean register(Context context, Bundle properties) {
@@ -116,7 +118,7 @@
int outputCount = mOutputStreams.length;
MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount];
for (int port = 0; port < inputCount; port++) {
- inputPortReceivers[port] = mEventScheduler.getReceiver(port);
+ inputPortReceivers[port] = mEventSchedulers[port].getReceiver();
}
mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount,
@@ -126,7 +128,7 @@
}
final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
- // Create input thread
+ // Create input thread which will read from all input ports
new Thread("UsbMidiDevice input thread") {
@Override
public void run() {
@@ -161,38 +163,46 @@
}
}.start();
- // Create output thread
- new Thread("UsbMidiDevice output thread") {
- @Override
- public void run() {
- while (true) {
- MidiEvent event;
- try {
- event = (MidiEvent)mEventScheduler.waitNextEvent();
- } catch (InterruptedException e) {
- // try again
- continue;
+ // Create output thread for each output port
+ for (int port = 0; port < outputCount; port++) {
+ final MidiEventScheduler eventSchedulerF = mEventSchedulers[port];
+ final FileOutputStream outputStreamF = mOutputStreams[port];
+ final int portF = port;
+
+ new Thread("UsbMidiDevice output thread " + port) {
+ @Override
+ public void run() {
+ while (true) {
+ MidiEvent event;
+ try {
+ event = (MidiEvent)eventSchedulerF.waitNextEvent();
+ } catch (InterruptedException e) {
+ // try again
+ continue;
+ }
+ if (event == null) {
+ break;
+ }
+ try {
+ outputStreamF.write(event.data, 0, event.count);
+ } catch (IOException e) {
+ Log.e(TAG, "write failed for port " + portF);
+ }
+ eventSchedulerF.addEventToPool(event);
}
- if (event == null) {
- break;
- }
- try {
- mOutputStreams[event.portNumber].write(event.data, 0, event.count);
- } catch (IOException e) {
- Log.e(TAG, "write failed for port " + event.portNumber);
- }
- mEventScheduler.addEventToPool(event);
+ Log.d(TAG, "output thread exit");
}
- Log.d(TAG, "output thread exit");
- }
- }.start();
+ }.start();
+ }
return true;
}
@Override
public void close() throws IOException {
- mEventScheduler.close();
+ for (int i = 0; i < mEventSchedulers.length; i++) {
+ mEventSchedulers[i].close();
+ }
if (mServer != null) {
mServer.close();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 607df2d..fb83956 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -162,8 +162,8 @@
mBindIntent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
mBindIntent.setComponent(mSessionComponentName);
mBound = mContext.bindServiceAsUser(mBindIntent, this,
- Context.BIND_AUTO_CREATE|Context.BIND_WAIVE_PRIORITY
- |Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mUser));
+ Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
+ | Context.BIND_ALLOW_OOM_MANAGEMENT, new UserHandle(mUser));
if (mBound) {
try {
mIWindowManager.addWindowToken(mToken,
diff --git a/telecomm/java/android/telecom/AuthenticatorService.java b/telecomm/java/android/telecom/AuthenticatorService.java
index 39717c3..7aa105d 100644
--- a/telecomm/java/android/telecom/AuthenticatorService.java
+++ b/telecomm/java/android/telecom/AuthenticatorService.java
@@ -19,6 +19,7 @@
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.NetworkErrorException;
+import android.annotation.SystemApi;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
@@ -28,7 +29,10 @@
/**
* A generic stub account authenticator service often used for sync adapters that do not directly
* involve accounts.
+ *
+ * @hide
*/
+@SystemApi
public class AuthenticatorService extends Service {
private static Authenticator mAuthenticator;
diff --git a/telecomm/java/android/telecom/Voicemail.java b/telecomm/java/android/telecom/Voicemail.java
index a884c5f..186c199 100644
--- a/telecomm/java/android/telecom/Voicemail.java
+++ b/telecomm/java/android/telecom/Voicemail.java
@@ -16,13 +16,17 @@
package android.telecom;
+import android.annotation.SystemApi;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Represents a single voicemail stored in the voicemail content provider.
+ *
+ * @hide
*/
+@SystemApi
public class Voicemail implements Parcelable {
private final Long mTimestamp;
private final String mNumber;
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 70ac268..24bdb7a 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -157,6 +157,34 @@
in PendingIntent deliveryIntent);
/**
+ * Send a data SMS. Only for use internally.
+ *
+ * @param smsc the SMSC to send the message through, or NULL for the
+ * default SMSC
+ * @param data the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is sucessfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applicaitons,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ * @param subId the subId id.
+ */
+ void sendDataForSubscriberWithSelfPermissions(int subId, String callingPkg, in String destAddr,
+ in String scAddr, in int destPort, in byte[] data, in PendingIntent sentIntent,
+ in PendingIntent deliveryIntent);
+
+ /**
* Send an SMS.
*
* @param smsc the SMSC to send the message through, or NULL for the
@@ -211,6 +239,34 @@
in PendingIntent deliveryIntent);
/**
+ * Send an SMS. Internal use only.
+ *
+ * @param smsc the SMSC to send the message through, or NULL for the
+ * default SMSC
+ * @param text the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is sucessfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK<code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ * @param subId the subId on which the SMS has to be sent.
+ */
+ void sendTextForSubscriberWithSelfPermissions(in int subId, String callingPkg,
+ in String destAddr, in String scAddr, in String text, in PendingIntent sentIntent,
+ in PendingIntent deliveryIntent);
+
+ /**
* Inject an SMS PDU into the android platform.
*
* @param pdu is the byte array of pdu to be injected into android application framework
diff --git a/test-runner/src/android/test/mock/MockCursor.java b/test-runner/src/android/test/mock/MockCursor.java
index a37c6eb..28fa0f8 100644
--- a/test-runner/src/android/test/mock/MockCursor.java
+++ b/test-runner/src/android/test/mock/MockCursor.java
@@ -35,162 +35,209 @@
* </P>
*/
public class MockCursor implements Cursor {
+ @Override
public int getColumnCount() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public int getColumnIndex(String columnName) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public int getColumnIndexOrThrow(String columnName) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public String getColumnName(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public String[] getColumnNames() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public int getCount() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean isNull(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public int getInt(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public long getLong(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public short getShort(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public float getFloat(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public double getDouble(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public byte[] getBlob(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public String getString(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
+ public void setExtras(Bundle extras) {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
public Bundle getExtras() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public int getPosition() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean isAfterLast() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean isBeforeFirst() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean isFirst() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean isLast() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean move(int offset) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean moveToFirst() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean moveToLast() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean moveToNext() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean moveToPrevious() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean moveToPosition(int position) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
+ @Deprecated
public void deactivate() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public void close() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean isClosed() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
+ @Deprecated
public boolean requery() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public void registerContentObserver(ContentObserver observer) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public void registerDataSetObserver(DataSetObserver observer) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public Bundle respond(Bundle extras) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public boolean getWantsAllOnMoveCalls() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public void setNotificationUri(ContentResolver cr, Uri uri) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public Uri getNotificationUri() {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public void unregisterContentObserver(ContentObserver observer) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public void unregisterDataSetObserver(DataSetObserver observer) {
throw new UnsupportedOperationException("unimplemented mock method");
}
+ @Override
public int getType(int columnIndex) {
throw new UnsupportedOperationException("unimplemented mock method");
}
diff --git a/tests/Compatibility/Android.mk b/tests/Compatibility/Android.mk
index 0ec4d9d..c2f89dd 100644
--- a/tests/Compatibility/Android.mk
+++ b/tests/Compatibility/Android.mk
@@ -25,7 +25,7 @@
LOCAL_PACKAGE_NAME := AppCompatibilityTest
-
+LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/Compatibility/AndroidManifest.xml b/tests/Compatibility/AndroidManifest.xml
index 2884532..8ae5bc5 100644
--- a/tests/Compatibility/AndroidManifest.xml
+++ b/tests/Compatibility/AndroidManifest.xml
@@ -19,7 +19,7 @@
<application >
<uses-library android:name="android.test.runner" />
</application>
-
+ <uses-permission android:name="android.permission.REAL_GET_TASKS" />
<instrumentation
android:name=".AppCompatibilityRunner"
android:targetPackage="com.android.compatibilitytest"
diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk
index c5495a5..9956bd7 100644
--- a/tools/aapt/Android.mk
+++ b/tools/aapt/Android.mk
@@ -67,7 +67,7 @@
libziparchive-host
aaptCFlags := -DAAPT_VERSION=\"$(BUILD_NUMBER)\"
-aaptCFLAGS += -Wall -Werror
+aaptCFlags += -Wall -Werror
ifeq ($(HOST_OS),linux)
aaptHostLdLibs += -lrt -ldl -lpthread
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
index 063b4e6..e4738f5 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -412,7 +412,6 @@
int startX, int startY, int endX, int endY, int dX, int dY,
int* out_inset)
{
- bool opaque_within_inset = true;
uint8_t max_opacity = 0;
int inset = 0;
*out_inset = 0;
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 38d10cf..beb94fd 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -3056,7 +3056,6 @@
const sp<AaptDir>& d = dirs.itemAt(k);
const String8& dirName = d->getLeaf();
Vector<String8> startTags;
- const char* startTag = NULL;
const KeyedVector<String8, Vector<NamespaceAttributePair> >* tagAttrPairs = NULL;
if ((dirName == String8("layout")) || (strncmp(dirName.string(), "layout-", 7) == 0)) {
tagAttrPairs = &kLayoutTagAttrPairs;
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 24f8168..c5fccbf 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -3167,7 +3167,7 @@
if (!validResources[i]) {
sp<ConfigList> c = t->getOrderedConfigs().itemAt(i);
if (c != NULL) {
- fprintf(stderr, "%s: no entries written for %s/%s (0x%08x)\n", log_prefix,
+ fprintf(stderr, "%s: no entries written for %s/%s (0x%08zx)\n", log_prefix,
String8(typeName).string(), String8(c->getName()).string(),
Res_MAKEID(p->getAssignedId() - 1, ti, i));
}
@@ -4526,7 +4526,6 @@
const KeyedVector<String16, Item>& bag = e->getBag();
const size_t bagCount = bag.size();
for (size_t bi = 0; bi < bagCount; bi++) {
- const Item& item = bag.valueAt(bi);
const uint32_t attrId = getResId(bag.keyAt(bi), &attr16);
const int sdkLevel = getPublicAttributeSdkLevel(attrId);
if (sdkLevel > 1 && sdkLevel > config.sdkVersion && sdkLevel > minSdk) {
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 9cea176..14f558e 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -29,12 +29,14 @@
BinaryResourceParser.cpp \
ConfigDescription.cpp \
Files.cpp \
+ Flag.cpp \
JavaClassGenerator.cpp \
Linker.cpp \
Locale.cpp \
Logger.cpp \
ManifestParser.cpp \
ManifestValidator.cpp \
+ Png.cpp \
ResChunkPullParser.cpp \
Resolver.cpp \
Resource.cpp \
@@ -69,7 +71,10 @@
XliffXmlPullParser_test.cpp \
XmlFlattener_test.cpp
-cIncludes :=
+cIncludes := \
+ external/libpng \
+ external/libz
+
hostLdLibs :=
hostStaticLibs := \
@@ -78,7 +83,8 @@
liblog \
libcutils \
libexpat \
- libziparchive-host
+ libziparchive-host \
+ libpng
ifneq ($(strip $(USE_MINGW)),)
hostStaticLibs += libz
diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp
new file mode 100644
index 0000000..b1ee8e7
--- /dev/null
+++ b/tools/aapt2/Flag.cpp
@@ -0,0 +1,109 @@
+#include "Flag.h"
+#include "StringPiece.h"
+
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace flag {
+
+struct Flag {
+ std::string name;
+ std::string description;
+ std::function<void(const StringPiece&)> action;
+ bool required;
+ bool* flagResult;
+ bool parsed;
+};
+
+static std::vector<Flag> sFlags;
+static std::vector<std::string> sArgs;
+
+void optionalFlag(const StringPiece& name, const StringPiece& description,
+ std::function<void(const StringPiece&)> action) {
+ sFlags.push_back(
+ Flag{ name.toString(), description.toString(), action, false, nullptr, false });
+}
+
+void requiredFlag(const StringPiece& name, const StringPiece& description,
+ std::function<void(const StringPiece&)> action) {
+ sFlags.push_back(
+ Flag{ name.toString(), description.toString(), action, true, nullptr, false });
+}
+
+void optionalSwitch(const StringPiece& name, const StringPiece& description, bool* result) {
+ sFlags.push_back(
+ Flag{ name.toString(), description.toString(), {}, false, result, false });
+}
+
+static void usageAndDie(const StringPiece& command) {
+ std::cerr << command << " [options]";
+ for (const Flag& flag : sFlags) {
+ if (flag.required) {
+ std::cerr << " " << flag.name << " arg";
+ }
+ }
+ std::cerr << " files..." << std::endl << std::endl << "Options:" << std::endl;
+
+ for (const Flag& flag : sFlags) {
+ std::string command = flag.name;
+ if (!flag.flagResult) {
+ command += " arg ";
+ }
+ std::cerr << " " << std::setw(30) << std::left << command
+ << flag.description << std::endl;
+ }
+ exit(1);
+}
+
+void parse(int argc, char** argv, const StringPiece& command) {
+ for (int i = 0; i < argc; i++) {
+ const StringPiece arg(argv[i]);
+ if (*arg.data() != '-') {
+ sArgs.emplace_back(arg.toString());
+ continue;
+ }
+
+ bool match = false;
+ for (Flag& flag : sFlags) {
+ if (arg == flag.name) {
+ match = true;
+ flag.parsed = true;
+ if (flag.flagResult) {
+ *flag.flagResult = true;
+ } else {
+ i++;
+ if (i >= argc) {
+ std::cerr << flag.name << " missing argument." << std::endl
+ << std::endl;
+ usageAndDie(command);
+ }
+ flag.action(argv[i]);
+ }
+ break;
+ }
+ }
+
+ if (!match) {
+ std::cerr << "unknown option '" << arg << "'." << std::endl << std::endl;
+ usageAndDie(command);
+ }
+ }
+
+ for (const Flag& flag : sFlags) {
+ if (flag.required && !flag.parsed) {
+ std::cerr << "missing required flag " << flag.name << std::endl << std::endl;
+ usageAndDie(command);
+ }
+ }
+}
+
+const std::vector<std::string>& getArgs() {
+ return sArgs;
+}
+
+} // namespace flag
+} // namespace aapt
diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h
new file mode 100644
index 0000000..32f5f2c
--- /dev/null
+++ b/tools/aapt2/Flag.h
@@ -0,0 +1,28 @@
+#ifndef AAPT_FLAG_H
+#define AAPT_FLAG_H
+
+#include "StringPiece.h"
+
+#include <functional>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace flag {
+
+void requiredFlag(const StringPiece& name, const StringPiece& description,
+ std::function<void(const StringPiece&)> action);
+
+void optionalFlag(const StringPiece& name, const StringPiece& description,
+ std::function<void(const StringPiece&)> action);
+
+void optionalSwitch(const StringPiece& name, const StringPiece& description, bool* result);
+
+void parse(int argc, char** argv, const StringPiece& command);
+
+const std::vector<std::string>& getArgs();
+
+} // namespace flag
+} // namespace aapt
+
+#endif // AAPT_FLAG_H
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index cfc5874..3a4b444c 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -18,10 +18,12 @@
#include "BigBuffer.h"
#include "BinaryResourceParser.h"
#include "Files.h"
+#include "Flag.h"
#include "JavaClassGenerator.h"
#include "Linker.h"
#include "ManifestParser.h"
#include "ManifestValidator.h"
+#include "Png.h"
#include "ResourceParser.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
@@ -41,6 +43,7 @@
#include <iostream>
#include <sstream>
#include <sys/stat.h>
+#include <utils/Errors.h>
using namespace aapt;
@@ -107,12 +110,12 @@
* Collect files from 'root', filtering out any files that do not
* match the FileFilter 'filter'.
*/
-bool walkTree(const StringPiece& root, const FileFilter& filter,
- std::vector<Source>& outEntries) {
+bool walkTree(const Source& root, const FileFilter& filter,
+ std::vector<Source>* outEntries) {
bool error = false;
- for (const std::string& dirName : listFiles(root)) {
- std::string dir(root.toString());
+ for (const std::string& dirName : listFiles(root.path)) {
+ std::string dir = root.path;
appendPath(&dir, dirName);
FileType ft = getFileType(dir);
@@ -134,13 +137,11 @@
}
if (ft != FileType::kRegular) {
- Logger::error(Source{ file })
- << "not a regular file."
- << std::endl;
+ Logger::error(Source{ file }) << "not a regular file." << std::endl;
error = true;
continue;
}
- outEntries.emplace_back(Source{ file });
+ outEntries->push_back(Source{ file });
}
}
return !error;
@@ -171,9 +172,6 @@
}
bool loadResTable(android::ResTable* table, const Source& source) {
- // For NO_ERROR (which on Windows is a MACRO).
- using namespace android;
-
std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
if (!ifs) {
Logger::error(source) << strerror(errno) << std::endl;
@@ -190,7 +188,7 @@
char* buf = new char[dataSize];
ifs.read(buf, dataSize);
- bool result = table->add(buf, dataSize, -1, true) == NO_ERROR;
+ bool result = table->add(buf, dataSize, -1, true) == android::NO_ERROR;
delete [] buf;
return result;
@@ -323,13 +321,6 @@
}
}
- std::unique_ptr<FileReference> fileResource = makeFileReference(
- table->getValueStringPool(),
- util::utf16ToUtf8(name.entry) + ".xml",
- name.type,
- config);
- table->addResource(name, config, source.line(0), std::move(fileResource));
-
for (size_t level : sdkLevels) {
Logger::note(source)
<< "creating v" << level << " versioned file."
@@ -347,14 +338,15 @@
return true;
}
-struct CompileXml {
+struct CompileItem {
Source source;
ResourceName name;
ConfigDescription config;
+ std::string extension;
};
-bool compileXml(std::shared_ptr<Resolver> resolver, const CompileXml& item,
- const Source& outputSource, std::queue<CompileXml>* queue) {
+bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item,
+ const Source& outputSource, std::queue<CompileItem>* queue) {
std::ifstream in(item.source.path, std::ifstream::binary);
if (!in) {
Logger::error(item.source) << strerror(errno) << std::endl;
@@ -376,7 +368,7 @@
if (minStrippedSdk.value() > 0) {
// Something was stripped, so let's generate a new file
// with the version of the smallest SDK version stripped.
- CompileXml newWork = item;
+ CompileItem newWork = item;
newWork.config.sdkVersion = minStrippedSdk.value();
queue->push(newWork);
}
@@ -394,9 +386,51 @@
return true;
}
+bool compilePng(const Source& source, const Source& output) {
+ std::ifstream in(source.path, std::ifstream::binary);
+ if (!in) {
+ Logger::error(source) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ std::ofstream out(output.path, std::ofstream::binary);
+ if (!out) {
+ Logger::error(output) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ std::string err;
+ Png png;
+ if (!png.process(source, in, out, {}, &err)) {
+ Logger::error(source) << err << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool copyFile(const Source& source, const Source& output) {
+ std::ifstream in(source.path, std::ifstream::binary);
+ if (!in) {
+ Logger::error(source) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ std::ofstream out(output.path, std::ofstream::binary);
+ if (!out) {
+ Logger::error(output) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ if (out << in.rdbuf()) {
+ Logger::error(output) << strerror(errno) << std::endl;
+ return true;
+ }
+ return false;
+}
+
struct AaptOptions {
enum class Phase {
- LegacyFull,
+ Full,
Collect,
Link,
Compile,
@@ -411,16 +445,26 @@
// The location of the manifest file.
Source manifest;
- // The files to process.
- std::vector<Source> sources;
+ // The source directories to walk and find resource files.
+ std::vector<Source> sourceDirs;
+
+ // The resource files to process and collect.
+ std::vector<Source> collectFiles;
+
+ // The binary table files to link.
+ std::vector<Source> linkFiles;
+
+ // The resource files to compile.
+ std::vector<Source> compileFiles;
// The libraries these files may reference.
std::vector<Source> libraries;
- // Output directory.
+ // Output path. This can be a directory or file
+ // depending on the phase.
Source output;
- // Whether to generate a Java Class.
+ // Directory to in which to generate R.java.
Maybe<Source> generateJavaClass;
// Whether to output verbose details about
@@ -428,9 +472,8 @@
bool verbose = false;
};
-bool compileAndroidManifest(std::shared_ptr<Resolver> resolver, const AaptOptions& options) {
- using namespace android;
-
+bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver,
+ const AaptOptions& options) {
Source outSource = options.output;
appendPath(&outSource.path, "AndroidManifest.xml");
@@ -461,8 +504,8 @@
p += b.size;
}
- ResXMLTree tree;
- if (tree.setTo(data.get(), outBuffer.size()) != NO_ERROR) {
+ android::ResXMLTree tree;
+ if (tree.setTo(data.get(), outBuffer.size()) != android::NO_ERROR) {
return false;
}
@@ -496,246 +539,117 @@
return parser.parse(source, pullParser, outInfo);
}
-/**
- * Parses legacy options and walks the source directories collecting
- * files to process.
- */
-bool prepareLegacy(std::vector<StringPiece>::const_iterator argsIter,
- const std::vector<StringPiece>::const_iterator argsEndIter,
- AaptOptions &options) {
- options.phase = AaptOptions::Phase::LegacyFull;
-
- std::vector<StringPiece> sourceDirs;
- while (argsIter != argsEndIter) {
- if (*argsIter == "-S") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-S missing argument." << std::endl;
- return false;
- }
- sourceDirs.push_back(*argsIter);
- } else if (*argsIter == "-I") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-I missing argument." << std::endl;
- return false;
- }
- options.libraries.push_back(Source{ argsIter->toString() });
- } else if (*argsIter == "-M") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-M missing argument." << std::endl;
- return false;
- }
-
- if (!options.manifest.path.empty()) {
- Logger::error() << "multiple -M flags are not allowed." << std::endl;
- return false;
- }
- options.manifest.path = argsIter->toString();
- } else if (*argsIter == "-o") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-o missing argument." << std::endl;
- return false;
- }
- options.output = Source{ argsIter->toString() };
- } else if (*argsIter == "-J") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-J missing argument." << std::endl;
- return false;
- }
- options.generateJavaClass = make_value<Source>(Source{ argsIter->toString() });
- } else if (*argsIter == "-v") {
- options.verbose = true;
- } else {
- Logger::error() << "unrecognized option '" << *argsIter << "'." << std::endl;
- return false;
- }
-
- ++argsIter;
+static AaptOptions prepareArgs(int argc, char** argv) {
+ if (argc < 2) {
+ std::cerr << "no command specified." << std::endl;
+ exit(1);
}
- if (options.manifest.path.empty()) {
- Logger::error() << "must specify manifest file with -M." << std::endl;
- return false;
- }
+ const StringPiece command(argv[1]);
+ argc -= 2;
+ argv += 2;
- // Load the App's package name, etc.
- if (!loadAppInfo(options.manifest, &options.appInfo)) {
- return false;
- }
+ AaptOptions options;
- /**
- * Set up the file filter to ignore certain files.
- */
- const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
- FileFilter fileFilter;
- if (customIgnore && customIgnore[0]) {
- fileFilter.setPattern(customIgnore);
+ StringPiece outputDescription = "place output in file";
+ if (command == "package") {
+ options.phase = AaptOptions::Phase::Full;
+ outputDescription = "place output in directory";
+ } else if (command == "collect") {
+ options.phase = AaptOptions::Phase::Collect;
+ } else if (command == "link") {
+ options.phase = AaptOptions::Phase::Link;
+ } else if (command == "compile") {
+ options.phase = AaptOptions::Phase::Compile;
+ outputDescription = "place output in directory";
} else {
- fileFilter.setPattern(
- "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
+ std::cerr << "invalid command '" << command << "'." << std::endl;
+ exit(1);
}
- /*
- * Enumerate the files in each source directory.
- */
- for (const StringPiece& source : sourceDirs) {
- if (!walkTree(source, fileFilter, options.sources)) {
- return false;
+ if (options.phase == AaptOptions::Phase::Full) {
+ flag::requiredFlag("-S", "add a directory in which to find resources",
+ [&options](const StringPiece& arg) {
+ options.sourceDirs.push_back(Source{ arg.toString() });
+ });
+
+ flag::requiredFlag("-M", "path to AndroidManifest.xml",
+ [&options](const StringPiece& arg) {
+ options.manifest = Source{ arg.toString() };
+ });
+
+ flag::optionalFlag("-I", "add an Android APK to link against",
+ [&options](const StringPiece& arg) {
+ options.libraries.push_back(Source{ arg.toString() });
+ });
+
+ flag::optionalFlag("--java", "directory in which to generate R.java",
+ [&options](const StringPiece& arg) {
+ options.generateJavaClass = Source{ arg.toString() };
+ });
+
+ } else {
+ flag::requiredFlag("--package", "Android package name",
+ [&options](const StringPiece& arg) {
+ options.appInfo.package = util::utf8ToUtf16(arg);
+ });
+
+ if (options.phase != AaptOptions::Phase::Collect) {
+ flag::optionalFlag("-I", "add an Android APK to link against",
+ [&options](const StringPiece& arg) {
+ options.libraries.push_back(Source{ arg.toString() });
+ });
+ }
+
+ if (options.phase == AaptOptions::Phase::Link) {
+ flag::optionalFlag("--java", "directory in which to generate R.java",
+ [&options](const StringPiece& arg) {
+ options.generateJavaClass = Source{ arg.toString() };
+ });
}
}
- return true;
-}
-bool prepareCollect(std::vector<StringPiece>::const_iterator argsIter,
- const std::vector<StringPiece>::const_iterator argsEndIter,
- AaptOptions& options) {
- options.phase = AaptOptions::Phase::Collect;
+ // Common flags for all steps.
+ flag::requiredFlag("-o", outputDescription, [&options](const StringPiece& arg) {
+ options.output = Source{ arg.toString() };
+ });
+ flag::optionalSwitch("-v", "enables verbose logging", &options.verbose);
- while (argsIter != argsEndIter) {
- if (*argsIter == "--package") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "--package missing argument." << std::endl;
- return false;
- }
- options.appInfo.package = util::utf8ToUtf16(*argsIter);
- } else if (*argsIter == "-o") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-o missing argument." << std::endl;
- return false;
- }
- options.output = Source{ argsIter->toString() };
- } else if (*argsIter == "-v") {
- options.verbose = true;
- } else if (argsIter->data()[0] != '-') {
- options.sources.push_back(Source{ argsIter->toString() });
- } else {
- Logger::error()
- << "unknown option '"
- << *argsIter
- << "'."
- << std::endl;
- return false;
+ // Build the command string for output (eg. "aapt2 compile").
+ std::string fullCommand = "aapt2";
+ fullCommand += " ";
+ fullCommand += command.toString();
+
+ // Actually read the command line flags.
+ flag::parse(argc, argv, fullCommand);
+
+ // Copy all the remaining arguments.
+ if (options.phase == AaptOptions::Phase::Collect) {
+ for (const std::string& arg : flag::getArgs()) {
+ options.collectFiles.push_back(Source{ arg });
}
- ++argsIter;
- }
- return true;
-}
-
-bool prepareLink(std::vector<StringPiece>::const_iterator argsIter,
- const std::vector<StringPiece>::const_iterator argsEndIter,
- AaptOptions& options) {
- options.phase = AaptOptions::Phase::Link;
-
- while (argsIter != argsEndIter) {
- if (*argsIter == "--package") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "--package missing argument." << std::endl;
- return false;
- }
- options.appInfo.package = util::utf8ToUtf16(*argsIter);
- } else if (*argsIter == "-o") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-o missing argument." << std::endl;
- return false;
- }
- options.output = Source{ argsIter->toString() };
- } else if (*argsIter == "-I") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-I missing argument." << std::endl;
- return false;
- }
- options.libraries.push_back(Source{ argsIter->toString() });
- } else if (*argsIter == "--java") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "--java missing argument." << std::endl;
- return false;
- }
- options.generateJavaClass = make_value<Source>(Source{ argsIter->toString() });
- } else if (*argsIter == "-v") {
- options.verbose = true;
- } else if (argsIter->data()[0] != '-') {
- options.sources.push_back(Source{ argsIter->toString() });
- } else {
- Logger::error()
- << "unknown option '"
- << *argsIter
- << "'."
- << std::endl;
- return false;
+ } else if (options.phase == AaptOptions::Phase::Compile) {
+ for (const std::string& arg : flag::getArgs()) {
+ options.compileFiles.push_back(Source{ arg });
}
- ++argsIter;
- }
- return true;
-}
-
-bool prepareCompile(std::vector<StringPiece>::const_iterator argsIter,
- const std::vector<StringPiece>::const_iterator argsEndIter,
- AaptOptions& options) {
- options.phase = AaptOptions::Phase::Compile;
-
- while (argsIter != argsEndIter) {
- if (*argsIter == "--package") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "--package missing argument." << std::endl;
- return false;
- }
- options.appInfo.package = util::utf8ToUtf16(*argsIter);
- } else if (*argsIter == "-o") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-o missing argument." << std::endl;
- return false;
- }
- options.output = Source{ argsIter->toString() };
- } else if (*argsIter == "-I") {
- ++argsIter;
- if (argsIter == argsEndIter) {
- Logger::error() << "-I missing argument." << std::endl;
- return false;
- }
- options.libraries.push_back(Source{ argsIter->toString() });
- } else if (*argsIter == "-v") {
- options.verbose = true;
- } else if (argsIter->data()[0] != '-') {
- options.sources.push_back(Source{ argsIter->toString() });
- } else {
- Logger::error()
- << "unknown option '"
- << *argsIter
- << "'."
- << std::endl;
- return false;
+ } else if (options.phase == AaptOptions::Phase::Link) {
+ for (const std::string& arg : flag::getArgs()) {
+ options.linkFiles.push_back(Source{ arg });
}
- ++argsIter;
}
- return true;
+ return options;
}
-struct CollectValuesItem {
- Source source;
- ConfigDescription config;
-};
-
-bool collectValues(std::shared_ptr<ResourceTable> table, const CollectValuesItem& item) {
- std::ifstream in(item.source.path, std::ifstream::binary);
+static bool collectValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
+ const ConfigDescription& config) {
+ std::ifstream in(source.path, std::ifstream::binary);
if (!in) {
- Logger::error(item.source) << strerror(errno) << std::endl;
+ Logger::error(source) << strerror(errno) << std::endl;
return false;
}
std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
- ResourceParser parser(table, item.source, item.config, xmlParser);
+ ResourceParser parser(table, source, config, xmlParser);
return parser.parse();
}
@@ -750,7 +664,7 @@
* Resource file paths are expected to look like:
* [--/res/]type[-config]/name
*/
-Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
+static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
if (parts.size() < 2) {
Logger::error(source) << "bad resource path." << std::endl;
@@ -792,326 +706,48 @@
};
}
-static bool doLegacy(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
- const AaptOptions& options) {
+bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<Resolver>& resolver) {
+ const bool versionStyles = (options->phase == AaptOptions::Phase::Full ||
+ options->phase == AaptOptions::Phase::Link);
+ const bool verifyNoMissingSymbols = (options->phase == AaptOptions::Phase::Full ||
+ options->phase == AaptOptions::Phase::Link);
+ const bool compileFiles = (options->phase == AaptOptions::Phase::Full ||
+ options->phase == AaptOptions::Phase::Compile);
+ const bool flattenTable = (options->phase == AaptOptions::Phase::Full ||
+ options->phase == AaptOptions::Phase::Collect ||
+ options->phase == AaptOptions::Phase::Link);
+ const bool useExtendedChunks = options->phase == AaptOptions::Phase::Collect;
+
+ // Build the output table path.
+ Source outputTable = options->output;
+ if (options->phase == AaptOptions::Phase::Full) {
+ appendPath(&outputTable.path, "resources.arsc");
+ }
+
bool error = false;
- std::queue<CompileXml> xmlCompileQueue;
+ std::queue<CompileItem> compileQueue;
- //
- // Read values XML files and XML/PNG files.
- // Need to parse the resource type/config/filename.
- //
- for (const Source& source : options.sources) {
- Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
- if (!maybePathData) {
- return false;
- }
-
- const ResourcePathData& pathData = maybePathData.value();
- if (pathData.resourceDir == u"values") {
- if (options.verbose) {
- Logger::note(source) << "collecting values..." << std::endl;
- }
-
- error |= !collectValues(table, CollectValuesItem{ source, pathData.config });
- continue;
- }
-
- const ResourceType* type = parseResourceType(pathData.resourceDir);
- if (!type) {
- Logger::error(source)
- << "invalid resource type '"
- << pathData.resourceDir
- << "'."
- << std::endl;
- return false;
- }
-
- ResourceName resourceName = { table->getPackage(), *type, pathData.name };
- if (pathData.extension == "xml") {
- if (options.verbose) {
- Logger::note(source) << "collecting XML..." << std::endl;
- }
-
- error |= !collectXml(table, source, resourceName, pathData.config);
- xmlCompileQueue.push(CompileXml{
- source,
- resourceName,
- pathData.config
- });
+ // If source directories were specified, walk them looking for resource files.
+ if (!options->sourceDirs.empty()) {
+ const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
+ FileFilter fileFilter;
+ if (customIgnore && customIgnore[0]) {
+ fileFilter.setPattern(customIgnore);
} else {
- std::unique_ptr<FileReference> fileReference = makeFileReference(
- table->getValueStringPool(),
- util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
- *type, pathData.config);
-
- error |= !table->addResource(resourceName, pathData.config, source.line(0),
- std::move(fileReference));
+ fileFilter.setPattern(
+ "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
}
- }
- if (error) {
- return false;
- }
-
- versionStylesForCompat(table);
-
- //
- // Verify all references and data types.
- //
- Linker linker(table, resolver);
- if (!linker.linkAndValidate()) {
- Logger::error()
- << "linking failed."
- << std::endl;
- return false;
- }
-
- const auto& unresolvedRefs = linker.getUnresolvedReferences();
- if (!unresolvedRefs.empty()) {
- for (const auto& entry : unresolvedRefs) {
- for (const auto& source : entry.second) {
- Logger::error(source)
- << "unresolved symbol '"
- << entry.first
- << "'."
- << std::endl;
+ for (const Source& source : options->sourceDirs) {
+ if (!walkTree(source, fileFilter, &options->collectFiles)) {
+ return false;
}
}
- return false;
}
- //
- // Compile the XML files.
- //
- while (!xmlCompileQueue.empty()) {
- const CompileXml& item = xmlCompileQueue.front();
-
- // Create the output path from the resource name.
- std::stringstream outputPath;
- outputPath << item.name.type;
- if (item.config != ConfigDescription{}) {
- outputPath << "-" << item.config.toString();
- }
-
- Source outSource = options.output;
- appendPath(&outSource.path, "res");
- appendPath(&outSource.path, outputPath.str());
-
- if (!mkdirs(outSource.path)) {
- Logger::error(outSource) << strerror(errno) << std::endl;
- return false;
- }
-
- appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + ".xml");
-
- if (options.verbose) {
- Logger::note(outSource) << "compiling XML file." << std::endl;
- }
-
- error |= !compileXml(resolver, item, outSource, &xmlCompileQueue);
- xmlCompileQueue.pop();
- }
-
- if (error) {
- return false;
- }
-
- //
- // Compile the AndroidManifest.xml file.
- //
- if (!compileAndroidManifest(resolver, options)) {
- return false;
- }
-
- //
- // Generate the Java R class.
- //
- if (options.generateJavaClass) {
- Source outPath = options.generateJavaClass.value();
- if (options.verbose) {
- Logger::note()
- << "writing symbols to "
- << outPath
- << "."
- << std::endl;
- }
-
- for (std::string& part : util::split(util::utf16ToUtf8(table->getPackage()), '.')) {
- appendPath(&outPath.path, part);
- }
-
- if (!mkdirs(outPath.path)) {
- Logger::error(outPath) << strerror(errno) << std::endl;
- return false;
- }
-
- appendPath(&outPath.path, "R.java");
-
- std::ofstream fout(outPath.path);
- if (!fout) {
- Logger::error(outPath) << strerror(errno) << std::endl;
- return false;
- }
-
- JavaClassGenerator generator(table, JavaClassGenerator::Options{});
- if (!generator.generate(fout)) {
- Logger::error(outPath)
- << generator.getError()
- << "."
- << std::endl;
- return false;
- }
- }
-
- //
- // Flatten resource table.
- //
- if (table->begin() != table->end()) {
- BigBuffer buffer(1024);
- TableFlattener::Options tableOptions;
- tableOptions.useExtendedChunks = false;
- TableFlattener flattener(tableOptions);
- if (!flattener.flatten(&buffer, *table)) {
- Logger::error()
- << "failed to flatten resource table->"
- << std::endl;
- return false;
- }
-
- if (options.verbose) {
- Logger::note()
- << "Final resource table size="
- << util::formatSize(buffer.size())
- << std::endl;
- }
-
- std::string outTable(options.output.path);
- appendPath(&outTable, "resources.arsc");
-
- std::ofstream fout(outTable, std::ofstream::binary);
- if (!fout) {
- Logger::error(Source{outTable})
- << strerror(errno)
- << "."
- << std::endl;
- return false;
- }
-
- if (!util::writeAll(fout, buffer)) {
- Logger::error(Source{outTable})
- << strerror(errno)
- << "."
- << std::endl;
- return false;
- }
- fout.flush();
- }
- return true;
-}
-
-static bool doCollect(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
- const AaptOptions& options) {
- bool error = false;
-
- //
- // Read values XML files and XML/PNG files.
- // Need to parse the resource type/config/filename.
- //
- for (const Source& source : options.sources) {
- Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
- if (!maybePathData) {
- return false;
- }
-
- const ResourcePathData& pathData = maybePathData.value();
- if (pathData.resourceDir == u"values") {
- if (options.verbose) {
- Logger::note(source) << "collecting values..." << std::endl;
- }
-
- error |= !collectValues(table, CollectValuesItem{ source, pathData.config });
- continue;
- }
-
- const ResourceType* type = parseResourceType(pathData.resourceDir);
- if (!type) {
- Logger::error(source)
- << "invalid resource type '"
- << pathData.resourceDir
- << "'."
- << std::endl;
- return false;
- }
-
- ResourceName resourceName = { table->getPackage(), *type, pathData.name };
- if (pathData.extension == "xml") {
- if (options.verbose) {
- Logger::note(source) << "collecting XML..." << std::endl;
- }
-
- error |= !collectXml(table, source, resourceName, pathData.config);
- } else {
- std::unique_ptr<FileReference> fileReference = makeFileReference(
- table->getValueStringPool(),
- util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
- *type,
- pathData.config);
- error |= !table->addResource(resourceName, pathData.config, source.line(0),
- std::move(fileReference));
- }
- }
-
- if (error) {
- return false;
- }
-
- Linker linker(table, resolver);
- if (!linker.linkAndValidate()) {
- return false;
- }
-
- //
- // Flatten resource table->
- //
- if (table->begin() != table->end()) {
- BigBuffer buffer(1024);
- TableFlattener::Options tableOptions;
- tableOptions.useExtendedChunks = true;
- TableFlattener flattener(tableOptions);
- if (!flattener.flatten(&buffer, *table)) {
- Logger::error()
- << "failed to flatten resource table->"
- << std::endl;
- return false;
- }
-
- std::ofstream fout(options.output.path, std::ofstream::binary);
- if (!fout) {
- Logger::error(options.output)
- << strerror(errno)
- << "."
- << std::endl;
- return false;
- }
-
- if (!util::writeAll(fout, buffer)) {
- Logger::error(options.output)
- << strerror(errno)
- << "."
- << std::endl;
- return false;
- }
- fout.flush();
- }
- return true;
-}
-
-static bool doLink(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
- const AaptOptions& options) {
- bool error = false;
-
- for (const Source& source : options.sources) {
+ // Load all binary resource tables.
+ for (const Source& source : options->linkFiles) {
error |= !loadBinaryResourceTable(table, source);
}
@@ -1119,41 +755,164 @@
return false;
}
- versionStylesForCompat(table);
+ // Collect all the resource files.
+ // Need to parse the resource type/config/filename.
+ for (const Source& source : options->collectFiles) {
+ Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+ if (!maybePathData) {
+ return false;
+ }
+ const ResourcePathData& pathData = maybePathData.value();
+ if (pathData.resourceDir == u"values") {
+ if (options->verbose) {
+ Logger::note(source) << "collecting values..." << std::endl;
+ }
+
+ error |= !collectValues(table, source, pathData.config);
+ continue;
+ }
+
+ const ResourceType* type = parseResourceType(pathData.resourceDir);
+ if (!type) {
+ Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
+ << std::endl;
+ return false;
+ }
+
+ ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+
+ // Add the file name to the resource table.
+ std::unique_ptr<FileReference> fileReference = makeFileReference(
+ table->getValueStringPool(),
+ util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
+ *type, pathData.config);
+ error |= !table->addResource(resourceName, pathData.config, source.line(0),
+ std::move(fileReference));
+
+ if (pathData.extension == "xml") {
+ error |= !collectXml(table, source, resourceName, pathData.config);
+ }
+
+ compileQueue.push(
+ CompileItem{ source, resourceName, pathData.config, pathData.extension });
+ }
+
+ if (error) {
+ return false;
+ }
+
+ // Version all styles referencing attributes outside of their specified SDK version.
+ if (versionStyles) {
+ versionStylesForCompat(table);
+ }
+
+ // Verify that all references are valid.
Linker linker(table, resolver);
if (!linker.linkAndValidate()) {
return false;
}
- const auto& unresolvedRefs = linker.getUnresolvedReferences();
- if (!unresolvedRefs.empty()) {
- for (const auto& entry : unresolvedRefs) {
- for (const auto& source : entry.second) {
- Logger::error(source)
- << "unresolved symbol '"
- << entry.first
- << "'."
- << std::endl;
+ // Verify that all symbols exist.
+ if (verifyNoMissingSymbols) {
+ const auto& unresolvedRefs = linker.getUnresolvedReferences();
+ if (!unresolvedRefs.empty()) {
+ for (const auto& entry : unresolvedRefs) {
+ for (const auto& source : entry.second) {
+ Logger::error(source) << "unresolved symbol '" << entry.first << "'."
+ << std::endl;
+ }
}
+ return false;
}
- return false;
}
- //
- // Generate the Java R class.
- //
- if (options.generateJavaClass) {
- Source outPath = options.generateJavaClass.value();
- if (options.verbose) {
- Logger::note()
- << "writing symbols to "
- << outPath
- << "."
- << std::endl;
+ // Compile files.
+ if (compileFiles) {
+ // First process any input compile files.
+ for (const Source& source : options->compileFiles) {
+ Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+ if (!maybePathData) {
+ return false;
+ }
+
+ const ResourcePathData& pathData = maybePathData.value();
+ const ResourceType* type = parseResourceType(pathData.resourceDir);
+ if (!type) {
+ Logger::error(source) << "invalid resource type '" << pathData.resourceDir
+ << "'." << std::endl;
+ return false;
+ }
+
+ ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+ compileQueue.push(
+ CompileItem{ source, resourceName, pathData.config, pathData.extension });
}
- for (std::string& part : util::split(util::utf16ToUtf8(table->getPackage()), '.')) {
+ // Now process the actual compile queue.
+ for (; !compileQueue.empty(); compileQueue.pop()) {
+ const CompileItem& item = compileQueue.front();
+
+ // Create the output directory path from the resource type and config.
+ std::stringstream outputPath;
+ outputPath << item.name.type;
+ if (item.config != ConfigDescription{}) {
+ outputPath << "-" << item.config.toString();
+ }
+
+ Source outSource = options->output;
+ appendPath(&outSource.path, "res");
+ appendPath(&outSource.path, outputPath.str());
+
+ // Make the directory.
+ if (!mkdirs(outSource.path)) {
+ Logger::error(outSource) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ // Add the file name to the directory path.
+ appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + "." + item.extension);
+
+ if (item.extension == "xml") {
+ if (options->verbose) {
+ Logger::note(outSource) << "compiling XML file." << std::endl;
+ }
+
+ error |= !compileXml(resolver, item, outSource, &compileQueue);
+ } else if (item.extension == "png" || item.extension == "9.png") {
+ if (options->verbose) {
+ Logger::note(outSource) << "compiling png file." << std::endl;
+ }
+
+ error |= !compilePng(item.source, outSource);
+ } else {
+ error |= !copyFile(item.source, outSource);
+ }
+ }
+
+ if (error) {
+ return false;
+ }
+ }
+
+ // Compile and validate the AndroidManifest.xml.
+ if (!options->manifest.path.empty()) {
+ if (!compileAndroidManifest(resolver, *options)) {
+ return false;
+ }
+ }
+
+ // Generate the Java class file.
+ if (options->generateJavaClass) {
+ Source outPath = options->generateJavaClass.value();
+ if (options->verbose) {
+ Logger::note() << "writing symbols to " << outPath << "." << std::endl;
+ }
+
+ // Build the output directory from the package name.
+ // Eg. com.android.app -> com/android/app
+ const std::string packageUtf8 = util::utf16ToUtf8(table->getPackage());
+ for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
appendPath(&outPath.path, part);
}
@@ -1170,52 +929,37 @@
return false;
}
- JavaClassGenerator generator(table, JavaClassGenerator::Options{});
+ JavaClassGenerator generator(table, {});
if (!generator.generate(fout)) {
- Logger::error(outPath)
- << generator.getError()
- << "."
- << std::endl;
+ Logger::error(outPath) << generator.getError() << "." << std::endl;
return false;
}
}
- //
- // Flatten resource table.
- //
- if (table->begin() != table->end()) {
+ // Flatten the resource table.
+ if (flattenTable && table->begin() != table->end()) {
BigBuffer buffer(1024);
TableFlattener::Options tableOptions;
- tableOptions.useExtendedChunks = false;
+ tableOptions.useExtendedChunks = useExtendedChunks;
TableFlattener flattener(tableOptions);
if (!flattener.flatten(&buffer, *table)) {
- Logger::error()
- << "failed to flatten resource table->"
- << std::endl;
+ Logger::error() << "failed to flatten resource table." << std::endl;
return false;
}
- if (options.verbose) {
- Logger::note()
- << "Final resource table size="
- << util::formatSize(buffer.size())
- << std::endl;
+ if (options->verbose) {
+ Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
+ << std::endl;
}
- std::ofstream fout(options.output.path, std::ofstream::binary);
+ std::ofstream fout(outputTable.path, std::ofstream::binary);
if (!fout) {
- Logger::error(options.output)
- << strerror(errno)
- << "."
- << std::endl;
+ Logger::error(outputTable) << strerror(errno) << "." << std::endl;
return false;
}
if (!util::writeAll(fout, buffer)) {
- Logger::error(options.output)
- << strerror(errno)
- << "."
- << std::endl;
+ Logger::error(outputTable) << strerror(errno) << "." << std::endl;
return false;
}
fout.flush();
@@ -1223,134 +967,24 @@
return true;
}
-static bool doCompile(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
- const AaptOptions& options) {
- std::queue<CompileXml> xmlCompileQueue;
-
- for (const Source& source : options.sources) {
- Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
- if (!maybePathData) {
- return false;
- }
-
- ResourcePathData& pathData = maybePathData.value();
- const ResourceType* type = parseResourceType(pathData.resourceDir);
- if (!type) {
- Logger::error(source)
- << "invalid resource type '"
- << pathData.resourceDir
- << "'."
- << std::endl;
- return false;
- }
-
- ResourceName resourceName = { table->getPackage(), *type, pathData.name };
- if (pathData.extension == "xml") {
- xmlCompileQueue.push(CompileXml{
- source,
- resourceName,
- pathData.config
- });
- } else {
- // TODO(adamlesinski): Handle images here.
- }
- }
-
- bool error = false;
- while (!xmlCompileQueue.empty()) {
- const CompileXml& item = xmlCompileQueue.front();
-
- // Create the output path from the resource name.
- std::stringstream outputPath;
- outputPath << item.name.type;
- if (item.config != ConfigDescription{}) {
- outputPath << "-" << item.config.toString();
- }
-
- Source outSource = options.output;
- appendPath(&outSource.path, "res");
- appendPath(&outSource.path, outputPath.str());
-
- if (!mkdirs(outSource.path)) {
- Logger::error(outSource) << strerror(errno) << std::endl;
- return false;
- }
-
- appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + ".xml");
-
- if (options.verbose) {
- Logger::note(outSource) << "compiling XML file." << std::endl;
- }
-
- error |= !compileXml(resolver, item, outSource, &xmlCompileQueue);
- xmlCompileQueue.pop();
- }
- return !error;
-}
-
int main(int argc, char** argv) {
Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
+ AaptOptions options = prepareArgs(argc, argv);
- std::vector<StringPiece> args;
- args.reserve(argc - 1);
- for (int i = 1; i < argc; i++) {
- args.emplace_back(argv[i], strlen(argv[i]));
+ // If we specified a manifest, go ahead and load the package name from the manifest.
+ if (!options.manifest.path.empty()) {
+ if (!loadAppInfo(options.manifest, &options.appInfo)) {
+ return false;
+ }
}
- if (args.empty()) {
- Logger::error() << "no command specified." << std::endl;
- return 1;
- }
-
- AaptOptions options;
-
- // Check the command we're running.
- const StringPiece& command = args.front();
- if (command == "package") {
- if (!prepareLegacy(std::begin(args) + 1, std::end(args), options)) {
- return 1;
- }
- } else if (command == "collect") {
- if (!prepareCollect(std::begin(args) + 1, std::end(args), options)) {
- return 1;
- }
- } else if (command == "link") {
- if (!prepareLink(std::begin(args) + 1, std::end(args), options)) {
- return 1;
- }
- } else if (command == "compile") {
- if (!prepareCompile(std::begin(args) + 1, std::end(args), options)) {
- return 1;
- }
- } else {
- Logger::error() << "unknown command '" << command << "'." << std::endl;
- return 1;
- }
-
- //
// Verify we have some common options set.
- //
-
- if (options.sources.empty()) {
- Logger::error() << "no sources specified." << std::endl;
- return false;
- }
-
- if (options.output.path.empty()) {
- Logger::error() << "no output directory specified." << std::endl;
- return false;
- }
-
if (options.appInfo.package.empty()) {
Logger::error() << "no package name specified." << std::endl;
return false;
}
-
- //
- // Every phase needs a resource table and a resolver/linker.
- //
-
+ // Every phase needs a resource table.
std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
table->setPackage(options.appInfo.package);
if (options.appInfo.package == u"android") {
@@ -1359,9 +993,7 @@
table->setPackageId(0x7f);
}
- //
// Load the included libraries.
- //
std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
for (const Source& source : options.libraries) {
if (util::stringEndsWith(source.path, ".arsc")) {
@@ -1393,33 +1025,9 @@
// Make the resolver that will cache IDs for us.
std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
- //
- // Dispatch to the real phase here.
- //
-
- bool result = true;
- switch (options.phase) {
- case AaptOptions::Phase::LegacyFull:
- result = doLegacy(table, resolver, options);
- break;
-
- case AaptOptions::Phase::Collect:
- result = doCollect(table, resolver, options);
- break;
-
- case AaptOptions::Phase::Link:
- result = doLink(table, resolver, options);
- break;
-
- case AaptOptions::Phase::Compile:
- result = doCompile(table, resolver, options);
- break;
- }
-
- if (!result) {
- Logger::error()
- << "aapt exiting with failures."
- << std::endl;
+ // Do the work.
+ if (!doAll(&options, table, resolver)) {
+ Logger::error() << "aapt exiting with failures." << std::endl;
return 1;
}
return 0;
diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/Png.cpp
new file mode 100644
index 0000000..dd753f1
--- /dev/null
+++ b/tools/aapt2/Png.cpp
@@ -0,0 +1,1284 @@
+/*
+ * 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.
+ */
+
+#include "Logger.h"
+#include "Png.h"
+#include "Source.h"
+#include "Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <iostream>
+#include <png.h>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <zlib.h>
+
+namespace aapt {
+
+constexpr bool kDebug = false;
+constexpr size_t kPngSignatureSize = 8u;
+
+struct PngInfo {
+ ~PngInfo() {
+ for (png_bytep row : rows) {
+ if (row != nullptr) {
+ delete[] row;
+ }
+ }
+
+ delete[] xDivs;
+ delete[] yDivs;
+ }
+
+ void* serialize9Patch() {
+ void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, yDivs,
+ colors.data());
+ reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
+ return serialized;
+ }
+
+ uint32_t width = 0;
+ uint32_t height = 0;
+ std::vector<png_bytep> rows;
+
+ bool is9Patch = false;
+ android::Res_png_9patch info9Patch;
+ int32_t* xDivs = nullptr;
+ int32_t* yDivs = nullptr;
+ std::vector<uint32_t> colors;
+
+ // Layout padding.
+ bool haveLayoutBounds = false;
+ int32_t layoutBoundsLeft;
+ int32_t layoutBoundsTop;
+ int32_t layoutBoundsRight;
+ int32_t layoutBoundsBottom;
+
+ // Round rect outline description.
+ int32_t outlineInsetsLeft;
+ int32_t outlineInsetsTop;
+ int32_t outlineInsetsRight;
+ int32_t outlineInsetsBottom;
+ float outlineRadius;
+ uint8_t outlineAlpha;
+};
+
+static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) {
+ std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
+ if (!input->read(reinterpret_cast<char*>(data), length)) {
+ png_error(readPtr, strerror(errno));
+ }
+}
+
+static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) {
+ std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr));
+ if (!output->write(reinterpret_cast<const char*>(data), length)) {
+ png_error(writePtr, strerror(errno));
+ }
+}
+
+static void flushDataToStream(png_structp writePtr) {
+ std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr));
+ if (!output->flush()) {
+ png_error(writePtr, strerror(errno));
+ }
+}
+
+static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
+ SourceLogger* logger = reinterpret_cast<SourceLogger*>(png_get_error_ptr(readPtr));
+ logger->warn() << warningMessage << "." << std::endl;
+}
+
+
+static bool readPng(png_structp readPtr, png_infop infoPtr, PngInfo* outInfo,
+ std::string* outError) {
+ if (setjmp(png_jmpbuf(readPtr))) {
+ *outError = "failed reading png";
+ return false;
+ }
+
+ png_set_sig_bytes(readPtr, kPngSignatureSize);
+ png_read_info(readPtr, infoPtr);
+
+ int colorType, bitDepth, interlaceType, compressionType;
+ png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType,
+ &interlaceType, &compressionType, nullptr);
+
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_palette_to_rgb(readPtr);
+ }
+
+ if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
+ png_set_expand_gray_1_2_4_to_8(readPtr);
+ }
+
+ if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
+ png_set_tRNS_to_alpha(readPtr);
+ }
+
+ if (bitDepth == 16) {
+ png_set_strip_16(readPtr);
+ }
+
+ if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
+ png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
+ }
+
+ if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(readPtr);
+ }
+
+ png_set_interlace_handling(readPtr);
+ png_read_update_info(readPtr, infoPtr);
+
+ const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
+ outInfo->rows.resize(outInfo->height);
+ for (size_t i = 0; i < outInfo->height; i++) {
+ outInfo->rows[i] = new png_byte[rowBytes];
+ }
+
+ png_read_image(readPtr, outInfo->rows.data());
+ png_read_end(readPtr, infoPtr);
+ return true;
+}
+
+static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, void* data) {
+ size_t patchSize = inPatch->serializedSize();
+ void* newData = malloc(patchSize);
+ memcpy(newData, data, patchSize);
+ android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
+ outPatch->fileToDevice();
+ // deserialization is done in place, so outPatch == newData
+ assert(outPatch == newData);
+ assert(outPatch->numXDivs == inPatch->numXDivs);
+ assert(outPatch->numYDivs == inPatch->numYDivs);
+ assert(outPatch->paddingLeft == inPatch->paddingLeft);
+ assert(outPatch->paddingRight == inPatch->paddingRight);
+ assert(outPatch->paddingTop == inPatch->paddingTop);
+ assert(outPatch->paddingBottom == inPatch->paddingBottom);
+/* for (int i = 0; i < outPatch->numXDivs; i++) {
+ assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]);
+ }
+ for (int i = 0; i < outPatch->numYDivs; i++) {
+ assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]);
+ }
+ for (int i = 0; i < outPatch->numColors; i++) {
+ assert(outPatch->getColors()[i] == inPatch->getColors()[i]);
+ }*/
+ free(newData);
+}
+
+/*static void dump_image(int w, int h, const png_byte* const* rows, int color_type) {
+ int i, j, rr, gg, bb, aa;
+
+ int bpp;
+ if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
+ bpp = 1;
+ } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ bpp = 2;
+ } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+ // We use a padding byte even when there is no alpha
+ bpp = 4;
+ } else {
+ printf("Unknown color type %d.\n", color_type);
+ }
+
+ for (j = 0; j < h; j++) {
+ const png_byte* row = rows[j];
+ for (i = 0; i < w; i++) {
+ rr = row[0];
+ gg = row[1];
+ bb = row[2];
+ aa = row[3];
+ row += bpp;
+
+ if (i == 0) {
+ printf("Row %d:", j);
+ }
+ switch (bpp) {
+ case 1:
+ printf(" (%d)", rr);
+ break;
+ case 2:
+ printf(" (%d %d", rr, gg);
+ break;
+ case 3:
+ printf(" (%d %d %d)", rr, gg, bb);
+ break;
+ case 4:
+ printf(" (%d %d %d %d)", rr, gg, bb, aa);
+ break;
+ }
+ if (i == (w - 1)) {
+ printf("\n");
+ }
+ }
+ }
+}*/
+
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#define ABS(a) ((a)<0?-(a):(a))
+
+static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int grayscaleTolerance,
+ png_colorp rgbPalette, png_bytep alphaPalette,
+ int *paletteEntries, bool *hasTransparency, int *colorType,
+ png_bytepp outRows) {
+ int w = imageInfo.width;
+ int h = imageInfo.height;
+ int i, j, rr, gg, bb, aa, idx;
+ uint32_t colors[256], col;
+ int num_colors = 0;
+ int maxGrayDeviation = 0;
+
+ bool isOpaque = true;
+ bool isPalette = true;
+ bool isGrayscale = true;
+
+ // Scan the entire image and determine if:
+ // 1. Every pixel has R == G == B (grayscale)
+ // 2. Every pixel has A == 255 (opaque)
+ // 3. There are no more than 256 distinct RGBA colors
+
+ if (kDebug) {
+ printf("Initial image data:\n");
+ //dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA);
+ }
+
+ for (j = 0; j < h; j++) {
+ const png_byte* row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
+
+ int odev = maxGrayDeviation;
+ maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
+ maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
+ if (maxGrayDeviation > odev) {
+ if (kDebug) {
+ printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
+ maxGrayDeviation, i, j, rr, gg, bb, aa);
+ }
+ }
+
+ // Check if image is really grayscale
+ if (isGrayscale) {
+ if (rr != gg || rr != bb) {
+ if (kDebug) {
+ printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
+ i, j, rr, gg, bb, aa);
+ }
+ isGrayscale = false;
+ }
+ }
+
+ // Check if image is really opaque
+ if (isOpaque) {
+ if (aa != 0xff) {
+ if (kDebug) {
+ printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
+ i, j, rr, gg, bb, aa);
+ }
+ isOpaque = false;
+ }
+ }
+
+ // Check if image is really <= 256 colors
+ if (isPalette) {
+ col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
+ bool match = false;
+ for (idx = 0; idx < num_colors; idx++) {
+ if (colors[idx] == col) {
+ match = true;
+ break;
+ }
+ }
+
+ // Write the palette index for the pixel to outRows optimistically
+ // We might overwrite it later if we decide to encode as gray or
+ // gray + alpha
+ *out++ = idx;
+ if (!match) {
+ if (num_colors == 256) {
+ if (kDebug) {
+ printf("Found 257th color at %d, %d\n", i, j);
+ }
+ isPalette = false;
+ } else {
+ colors[num_colors++] = col;
+ }
+ }
+ }
+ }
+ }
+
+ *paletteEntries = 0;
+ *hasTransparency = !isOpaque;
+ int bpp = isOpaque ? 3 : 4;
+ int paletteSize = w * h + bpp * num_colors;
+
+ if (kDebug) {
+ printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
+ printf("isOpaque = %s\n", isOpaque ? "true" : "false");
+ printf("isPalette = %s\n", isPalette ? "true" : "false");
+ printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
+ paletteSize, 2 * w * h, bpp * w * h);
+ printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance);
+ }
+
+ // Choose the best color type for the image.
+ // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
+ // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
+ // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
+ // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
+ // small, otherwise use COLOR_TYPE_RGB{_ALPHA}
+ if (isGrayscale) {
+ if (isOpaque) {
+ *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
+ } else {
+ // Use a simple heuristic to determine whether using a palette will
+ // save space versus using gray + alpha for each pixel.
+ // This doesn't take into account chunk overhead, filtering, LZ
+ // compression, etc.
+ if (isPalette && (paletteSize < 2 * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
+ } else {
+ *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
+ }
+ }
+ } else if (isPalette && (paletteSize < bpp * w * h)) {
+ *colorType = PNG_COLOR_TYPE_PALETTE;
+ } else {
+ if (maxGrayDeviation <= grayscaleTolerance) {
+ logger->note() << "forcing image to gray (max deviation = " << maxGrayDeviation
+ << ")."
+ << std::endl;
+ *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
+ } else {
+ *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+ }
+
+ // Perform postprocessing of the image or palette data based on the final
+ // color type chosen
+
+ if (*colorType == PNG_COLOR_TYPE_PALETTE) {
+ // Create separate RGB and Alpha palettes and set the number of colors
+ *paletteEntries = num_colors;
+
+ // Create the RGB and alpha palettes
+ for (int idx = 0; idx < num_colors; idx++) {
+ col = colors[idx];
+ rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff);
+ rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
+ rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff);
+ alphaPalette[idx] = (png_byte) (col & 0xff);
+ }
+ } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ // If the image is gray or gray + alpha, compact the pixels into outRows
+ for (j = 0; j < h; j++) {
+ const png_byte* row = imageInfo.rows[j];
+ png_bytep out = outRows[j];
+ for (i = 0; i < w; i++) {
+ rr = *row++;
+ gg = *row++;
+ bb = *row++;
+ aa = *row++;
+
+ if (isGrayscale) {
+ *out++ = rr;
+ } else {
+ *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+ }
+ if (!isOpaque) {
+ *out++ = aa;
+ }
+ }
+ }
+ }
+}
+
+static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
+ int grayScaleTolerance, SourceLogger* logger, std::string* outError) {
+ if (setjmp(png_jmpbuf(writePtr))) {
+ *outError = "failed to write png";
+ return false;
+ }
+
+ uint32_t width, height;
+ int colorType, bitDepth, interlaceType, compressionType;
+
+ png_unknown_chunk unknowns[3];
+ unknowns[0].data = nullptr;
+ unknowns[1].data = nullptr;
+ unknowns[2].data = nullptr;
+
+ png_bytepp outRows = (png_bytepp) malloc((int) info->height * sizeof(png_bytep));
+ if (outRows == (png_bytepp) 0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
+ }
+ for (uint32_t i = 0; i < info->height; i++) {
+ outRows[i] = (png_bytep) malloc(2 * (int) info->width);
+ if (outRows[i] == (png_bytep) 0) {
+ printf("Can't allocate output buffer!\n");
+ exit(1);
+ }
+ }
+
+ png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
+
+ if (kDebug) {
+ logger->note() << "writing image: w = " << info->width
+ << ", h = " << info->height
+ << std::endl;
+ }
+
+ png_color rgbPalette[256];
+ png_byte alphaPalette[256];
+ bool hasTransparency;
+ int paletteEntries;
+
+ analyze_image(logger, *info, grayScaleTolerance, rgbPalette, alphaPalette,
+ &paletteEntries, &hasTransparency, &colorType, outRows);
+
+ // If the image is a 9-patch, we need to preserve it as a ARGB file to make
+ // sure the pixels will not be pre-dithered/clamped until we decide they are
+ if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB ||
+ colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_PALETTE)) {
+ colorType = PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+
+ if (kDebug) {
+ switch (colorType) {
+ case PNG_COLOR_TYPE_PALETTE:
+ logger->note() << "has " << paletteEntries
+ << " colors" << (hasTransparency ? " (with alpha)" : "")
+ << ", using PNG_COLOR_TYPE_PALLETTE."
+ << std::endl;
+ break;
+ case PNG_COLOR_TYPE_GRAY:
+ logger->note() << "is opaque gray, using PNG_COLOR_TYPE_GRAY." << std::endl;
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ logger->note() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA." << std::endl;
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ logger->note() << "is opaque RGB, using PNG_COLOR_TYPE_RGB." << std::endl;
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ logger->note() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA." << std::endl;
+ break;
+ }
+ }
+
+ png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
+ if (hasTransparency) {
+ png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p) 0);
+ }
+ png_set_filter(writePtr, 0, PNG_NO_FILTERS);
+ } else {
+ png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
+ }
+
+ if (info->is9Patch) {
+ int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0);
+ int pIndex = info->haveLayoutBounds ? 2 : 1;
+ int bIndex = 1;
+ int oIndex = 0;
+
+ // Chunks ordered thusly because older platforms depend on the base 9 patch data being last
+ png_bytep chunkNames = info->haveLayoutBounds
+ ? (png_bytep)"npOl\0npLb\0npTc\0"
+ : (png_bytep)"npOl\0npTc";
+
+ // base 9 patch data
+ if (kDebug) {
+ logger->note() << "adding 9-patch info..." << std::endl;
+ }
+ strcpy((char*)unknowns[pIndex].name, "npTc");
+ unknowns[pIndex].data = (png_byte*) info->serialize9Patch();
+ unknowns[pIndex].size = info->info9Patch.serializedSize();
+ // TODO: remove the check below when everything works
+ checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data);
+
+ // automatically generated 9 patch outline data
+ int chunkSize = sizeof(png_uint_32) * 6;
+ strcpy((char*)unknowns[oIndex].name, "npOl");
+ unknowns[oIndex].data = (png_byte*) calloc(chunkSize, 1);
+ png_byte outputData[chunkSize];
+ memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
+ ((float*) outputData)[4] = info->outlineRadius;
+ ((png_uint_32*) outputData)[5] = info->outlineAlpha;
+ memcpy(unknowns[oIndex].data, &outputData, chunkSize);
+ unknowns[oIndex].size = chunkSize;
+
+ // optional optical inset / layout bounds data
+ if (info->haveLayoutBounds) {
+ int chunkSize = sizeof(png_uint_32) * 4;
+ strcpy((char*)unknowns[bIndex].name, "npLb");
+ unknowns[bIndex].data = (png_byte*) calloc(chunkSize, 1);
+ memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
+ unknowns[bIndex].size = chunkSize;
+ }
+
+ for (int i = 0; i < chunkCount; i++) {
+ unknowns[i].location = PNG_HAVE_PLTE;
+ }
+ png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS,
+ chunkNames, chunkCount);
+ png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
+
+#if PNG_LIBPNG_VER < 10600
+ // Deal with unknown chunk location bug in 1.5.x and earlier.
+ png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE);
+ if (info->haveLayoutBounds) {
+ png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE);
+ }
+#endif
+ }
+
+ png_write_info(writePtr, infoPtr);
+
+ png_bytepp rows;
+ if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
+ if (colorType == PNG_COLOR_TYPE_RGB) {
+ png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
+ }
+ rows = info->rows.data();
+ } else {
+ rows = outRows;
+ }
+ png_write_image(writePtr, rows);
+
+ if (kDebug) {
+ printf("Final image data:\n");
+ //dump_image(info->width, info->height, rows, colorType);
+ }
+
+ png_write_end(writePtr, infoPtr);
+
+ for (uint32_t i = 0; i < info->height; i++) {
+ free(outRows[i]);
+ }
+ free(outRows);
+ free(unknowns[0].data);
+ free(unknowns[1].data);
+ free(unknowns[2].data);
+
+ png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType,
+ &compressionType, nullptr);
+
+ if (kDebug) {
+ logger->note() << "image written: w = " << width << ", h = " << height
+ << ", d = " << bitDepth << ", colors = " << colorType
+ << ", inter = " << interlaceType << ", comp = " << compressionType
+ << std::endl;
+ }
+ return true;
+}
+
+constexpr uint32_t kColorWhite = 0xffffffffu;
+constexpr uint32_t kColorTick = 0xff000000u;
+constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu;
+
+enum class TickType {
+ kNone,
+ kTick,
+ kLayoutBounds,
+ kBoth
+};
+
+static TickType tickType(png_bytep p, bool transparent, const char** outError) {
+ png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+
+ if (transparent) {
+ if (p[3] == 0) {
+ return TickType::kNone;
+ }
+ if (color == kColorLayoutBoundsTick) {
+ return TickType::kLayoutBounds;
+ }
+ if (color == kColorTick) {
+ return TickType::kTick;
+ }
+
+ // Error cases
+ if (p[3] != 0xff) {
+ *outError = "Frame pixels must be either solid or transparent "
+ "(not intermediate alphas)";
+ return TickType::kNone;
+ }
+
+ if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+ *outError = "Ticks in transparent frame must be black or red";
+ }
+ return TickType::kTick;
+ }
+
+ if (p[3] != 0xFF) {
+ *outError = "White frame must be a solid color (no alpha)";
+ }
+ if (color == kColorWhite) {
+ return TickType::kNone;
+ }
+ if (color == kColorTick) {
+ return TickType::kTick;
+ }
+ if (color == kColorLayoutBoundsTick) {
+ return TickType::kLayoutBounds;
+ }
+
+ if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+ *outError = "Ticks in white frame must be black or red";
+ return TickType::kNone;
+ }
+ return TickType::kTick;
+}
+
+enum class TickState {
+ kStart,
+ kInside1,
+ kOutside1
+};
+
+static bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required,
+ int32_t* outLeft, int32_t* outRight, const char** outError,
+ uint8_t* outDivs, bool multipleAllowed) {
+ *outLeft = *outRight = -1;
+ TickState state = TickState::kStart;
+ bool found = false;
+
+ for (int i = 1; i < width - 1; i++) {
+ if (tickType(row+i*4, transparent, outError) == TickType::kTick) {
+ if (state == TickState::kStart ||
+ (state == TickState::kOutside1 && multipleAllowed)) {
+ *outLeft = i-1;
+ *outRight = width-2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
+ }
+ state = TickState::kInside1;
+ } else if (state == TickState::kOutside1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outLeft = i;
+ return false;
+ }
+ } else if (!*outError) {
+ if (state == TickState::kInside1) {
+ // We're done with this div. Move on to the next.
+ *outRight = i-1;
+ outRight += 2;
+ outLeft += 2;
+ state = TickState::kOutside1;
+ }
+ } else {
+ *outLeft = i;
+ return false;
+ }
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outLeft = -1;
+ return false;
+ }
+ return true;
+}
+
+static bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent,
+ bool required, int32_t* outTop, int32_t* outBottom,
+ const char** outError, uint8_t* outDivs, bool multipleAllowed) {
+ *outTop = *outBottom = -1;
+ TickState state = TickState::kStart;
+ bool found = false;
+
+ for (int i = 1; i < height - 1; i++) {
+ if (tickType(rows[i]+offset, transparent, outError) == TickType::kTick) {
+ if (state == TickState::kStart ||
+ (state == TickState::kOutside1 && multipleAllowed)) {
+ *outTop = i-1;
+ *outBottom = height-2;
+ found = true;
+ if (outDivs != NULL) {
+ *outDivs += 2;
+ }
+ state = TickState::kInside1;
+ } else if (state == TickState::kOutside1) {
+ *outError = "Can't have more than one marked region along edge";
+ *outTop = i;
+ return false;
+ }
+ } else if (!*outError) {
+ if (state == TickState::kInside1) {
+ // We're done with this div. Move on to the next.
+ *outBottom = i-1;
+ outTop += 2;
+ outBottom += 2;
+ state = TickState::kOutside1;
+ }
+ } else {
+ *outTop = i;
+ return false;
+ }
+ }
+
+ if (required && !found) {
+ *outError = "No marked region found along edge";
+ *outTop = -1;
+ return false;
+ }
+ return true;
+}
+
+static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent,
+ bool /* required */, int32_t* outLeft,
+ int32_t* outRight, const char** outError) {
+ *outLeft = *outRight = 0;
+
+ // Look for left tick
+ if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) {
+ // Starting with a layout padding tick
+ int i = 1;
+ while (i < width - 1) {
+ (*outLeft)++;
+ i++;
+ if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+
+ // Look for right tick
+ if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) {
+ // Ending with a layout padding tick
+ int i = width - 2;
+ while (i > 1) {
+ (*outRight)++;
+ i--;
+ if (tickType(row+i*4, transparent, outError) != TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent,
+ bool /* required */, int32_t* outTop, int32_t* outBottom,
+ const char** outError) {
+ *outTop = *outBottom = 0;
+
+ // Look for top tick
+ if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) {
+ // Starting with a layout padding tick
+ int i = 1;
+ while (i < height - 1) {
+ (*outTop)++;
+ i++;
+ if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+
+ // Look for bottom tick
+ if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) {
+ // Ending with a layout padding tick
+ int i = height - 2;
+ while (i > 1) {
+ (*outBottom)++;
+ i--;
+ if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY,
+ int dX, int dY, int* outInset) {
+ uint8_t maxOpacity = 0;
+ int inset = 0;
+ *outInset = 0;
+ for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
+ png_byte* color = rows[y] + x * 4;
+ uint8_t opacity = color[3];
+ if (opacity > maxOpacity) {
+ maxOpacity = opacity;
+ *outInset = inset;
+ }
+ if (opacity == 0xff) return;
+ }
+}
+
+static uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) {
+ uint8_t maxAlpha = 0;
+ for (int x = startX; x < endX; x++) {
+ uint8_t alpha = (row + x * 4)[3];
+ if (alpha > maxAlpha) maxAlpha = alpha;
+ }
+ return maxAlpha;
+}
+
+static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) {
+ uint8_t maxAlpha = 0;
+ for (int y = startY; y < endY; y++) {
+ uint8_t alpha = (rows[y] + offsetX * 4)[3];
+ if (alpha > maxAlpha) maxAlpha = alpha;
+ }
+ return maxAlpha;
+}
+
+static void getOutline(PngInfo* image) {
+ int midX = image->width / 2;
+ int midY = image->height / 2;
+ int endX = image->width - 2;
+ int endY = image->height - 2;
+
+ // find left and right extent of nine patch content on center row
+ if (image->width > 4) {
+ findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
+ findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
+ &image->outlineInsetsRight);
+ } else {
+ image->outlineInsetsLeft = 0;
+ image->outlineInsetsRight = 0;
+ }
+
+ // find top and bottom extent of nine patch content on center column
+ if (image->height > 4) {
+ findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
+ findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
+ &image->outlineInsetsBottom);
+ } else {
+ image->outlineInsetsTop = 0;
+ image->outlineInsetsBottom = 0;
+ }
+
+ int innerStartX = 1 + image->outlineInsetsLeft;
+ int innerStartY = 1 + image->outlineInsetsTop;
+ int innerEndX = endX - image->outlineInsetsRight;
+ int innerEndY = endY - image->outlineInsetsBottom;
+ int innerMidX = (innerEndX + innerStartX) / 2;
+ int innerMidY = (innerEndY + innerStartY) / 2;
+
+ // assuming the image is a round rect, compute the radius by marching
+ // diagonally from the top left corner towards the center
+ image->outlineAlpha = std::max(
+ maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
+ maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
+
+ int diagonalInset = 0;
+ findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
+ &diagonalInset);
+
+ /* Determine source radius based upon inset:
+ * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
+ * sqrt(2) * r = sqrt(2) * i + r
+ * (sqrt(2) - 1) * r = sqrt(2) * i
+ * r = sqrt(2) / (sqrt(2) - 1) * i
+ */
+ image->outlineRadius = 3.4142f * diagonalInset;
+
+ if (kDebug) {
+ printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
+ image->outlineInsetsLeft,
+ image->outlineInsetsTop,
+ image->outlineInsetsRight,
+ image->outlineInsetsBottom,
+ image->outlineRadius,
+ image->outlineAlpha);
+ }
+}
+
+static uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) {
+ png_bytep color = rows[top] + left*4;
+
+ if (left > right || top > bottom) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
+
+ while (top <= bottom) {
+ for (int i = left; i <= right; i++) {
+ png_bytep p = rows[top]+i*4;
+ if (color[3] == 0) {
+ if (p[3] != 0) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
+ } else if (p[0] != color[0] || p[1] != color[1] ||
+ p[2] != color[2] || p[3] != color[3]) {
+ return android::Res_png_9patch::NO_COLOR;
+ }
+ }
+ top++;
+ }
+
+ if (color[3] == 0) {
+ return android::Res_png_9patch::TRANSPARENT_COLOR;
+ }
+ return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
+}
+
+static bool do9Patch(PngInfo* image, std::string* outError) {
+ image->is9Patch = true;
+
+ int W = image->width;
+ int H = image->height;
+ int i, j;
+
+ const int maxSizeXDivs = W * sizeof(int32_t);
+ const int maxSizeYDivs = H * sizeof(int32_t);
+ int32_t* xDivs = image->xDivs = new int32_t[W];
+ int32_t* yDivs = image->yDivs = new int32_t[H];
+ uint8_t numXDivs = 0;
+ uint8_t numYDivs = 0;
+
+ int8_t numColors;
+ int numRows;
+ int numCols;
+ int top;
+ int left;
+ int right;
+ int bottom;
+ memset(xDivs, -1, maxSizeXDivs);
+ memset(yDivs, -1, maxSizeYDivs);
+ image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1;
+ image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
+ image->layoutBoundsLeft = image->layoutBoundsRight = 0;
+ image->layoutBoundsTop = image->layoutBoundsBottom = 0;
+
+ png_bytep p = image->rows[0];
+ bool transparent = p[3] == 0;
+ bool hasColor = false;
+
+ const char* errorMsg = nullptr;
+ int errorPixel = -1;
+ const char* errorEdge = nullptr;
+
+ int colorIndex = 0;
+ std::vector<png_bytep> newRows;
+
+ // Validate size...
+ if (W < 3 || H < 3) {
+ errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
+ goto getout;
+ }
+
+ // Validate frame...
+ if (!transparent &&
+ (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
+ errorMsg = "Must have one-pixel frame that is either transparent or white";
+ goto getout;
+ }
+
+ // Find left and right of sizing areas...
+ if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs,
+ true)) {
+ errorPixel = xDivs[0];
+ errorEdge = "top";
+ goto getout;
+ }
+
+ // Find top and bottom of sizing areas...
+ if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1],
+ &errorMsg, &numYDivs, true)) {
+ errorPixel = yDivs[0];
+ errorEdge = "left";
+ goto getout;
+ }
+
+ // Copy patch size data into image...
+ image->info9Patch.numXDivs = numXDivs;
+ image->info9Patch.numYDivs = numYDivs;
+
+ // Find left and right of padding area...
+ if (!getHorizontalTicks(image->rows[H-1], W, transparent, false,
+ &image->info9Patch.paddingLeft, &image->info9Patch.paddingRight,
+ &errorMsg, nullptr, false)) {
+ errorPixel = image->info9Patch.paddingLeft;
+ errorEdge = "bottom";
+ goto getout;
+ }
+
+ // Find top and bottom of padding area...
+ if (!getVerticalTicks(image->rows.data(), (W-1)*4, H, transparent, false,
+ &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom,
+ &errorMsg, nullptr, false)) {
+ errorPixel = image->info9Patch.paddingTop;
+ errorEdge = "right";
+ goto getout;
+ }
+
+ // Find left and right of layout padding...
+ getHorizontalLayoutBoundsTicks(image->rows[H-1], W, transparent, false,
+ &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg);
+
+ getVerticalLayoutBoundsTicks(image->rows.data(), (W-1)*4, H, transparent, false,
+ &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg);
+
+ image->haveLayoutBounds = image->layoutBoundsLeft != 0
+ || image->layoutBoundsRight != 0
+ || image->layoutBoundsTop != 0
+ || image->layoutBoundsBottom != 0;
+
+ if (image->haveLayoutBounds) {
+ if (kDebug) {
+ printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
+ image->layoutBoundsRight, image->layoutBoundsBottom);
+ }
+ }
+
+ // use opacity of pixels to estimate the round rect outline
+ getOutline(image);
+
+ // If padding is not yet specified, take values from size.
+ if (image->info9Patch.paddingLeft < 0) {
+ image->info9Patch.paddingLeft = xDivs[0];
+ image->info9Patch.paddingRight = W - 2 - xDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
+ }
+ if (image->info9Patch.paddingTop < 0) {
+ image->info9Patch.paddingTop = yDivs[0];
+ image->info9Patch.paddingBottom = H - 2 - yDivs[1];
+ } else {
+ // Adjust value to be correct!
+ image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
+ }
+
+/* if (kDebug) {
+ printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
+ xDivs[0], xDivs[1],
+ yDivs[0], yDivs[1]);
+ printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
+ image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
+ image->info9Patch.paddingTop, image->info9Patch.paddingBottom);
+ }*/
+
+ // Remove frame from image.
+ newRows.resize(H - 2);
+ for (i = 0; i < H - 2; i++) {
+ newRows[i] = image->rows[i + 1];
+ memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
+ }
+ image->rows.swap(newRows);
+
+ image->width -= 2;
+ W = image->width;
+ image->height -= 2;
+ H = image->height;
+
+ // Figure out the number of rows and columns in the N-patch
+ numCols = numXDivs + 1;
+ if (xDivs[0] == 0) { // Column 1 is strechable
+ numCols--;
+ }
+ if (xDivs[numXDivs - 1] == W) {
+ numCols--;
+ }
+ numRows = numYDivs + 1;
+ if (yDivs[0] == 0) { // Row 1 is strechable
+ numRows--;
+ }
+ if (yDivs[numYDivs - 1] == H) {
+ numRows--;
+ }
+
+ // Make sure the amount of rows and columns will fit in the number of
+ // colors we can use in the 9-patch format.
+ if (numRows * numCols > 0x7F) {
+ errorMsg = "Too many rows and columns in 9-patch perimeter";
+ goto getout;
+ }
+
+ numColors = numRows * numCols;
+ image->info9Patch.numColors = numColors;
+ image->colors.resize(numColors);
+
+ // Fill in color information for each patch.
+
+ uint32_t c;
+ top = 0;
+
+ // The first row always starts with the top being at y=0 and the bottom
+ // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case
+ // the first row is stretchable along the Y axis, otherwise it is fixed.
+ // The last row always ends with the bottom being bitmap.height and the top
+ // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+ // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+ // the Y axis, otherwise it is fixed.
+ //
+ // The first and last columns are similarly treated with respect to the X
+ // axis.
+ //
+ // The above is to help explain some of the special casing that goes on the
+ // code below.
+
+ // The initial yDiv and whether the first row is considered stretchable or
+ // not depends on whether yDiv[0] was zero or not.
+ for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) {
+ if (j == numYDivs) {
+ bottom = H;
+ } else {
+ bottom = yDivs[j];
+ }
+ left = 0;
+ // The initial xDiv and whether the first column is considered
+ // stretchable or not depends on whether xDiv[0] was zero or not.
+ for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) {
+ if (i == numXDivs) {
+ right = W;
+ } else {
+ right = xDivs[i];
+ }
+ c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
+ image->colors[colorIndex++] = c;
+ if (kDebug) {
+ if (c != android::Res_png_9patch::NO_COLOR) {
+ hasColor = true;
+ }
+ }
+ left = right;
+ }
+ top = bottom;
+ }
+
+ assert(colorIndex == numColors);
+
+ if (kDebug && hasColor) {
+ for (i = 0; i < numColors; i++) {
+ if (i == 0) printf("Colors:\n");
+ printf(" #%08x", image->colors[i]);
+ if (i == numColors - 1) printf("\n");
+ }
+ }
+getout:
+ if (errorMsg) {
+ std::stringstream err;
+ err << "9-patch malformed: " << errorMsg;
+ if (!errorEdge) {
+ err << "." << std::endl;
+ if (errorPixel >= 0) {
+ err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge";
+ } else {
+ err << "Found along " << errorEdge << " edge";
+ }
+ }
+ *outError = err.str();
+ return false;
+ }
+ return true;
+}
+
+
+bool Png::process(const Source& source, std::istream& input, std::ostream& output,
+ const Options& options, std::string* outError) {
+ png_byte signature[kPngSignatureSize];
+
+ // Read the PNG signature first.
+ if (!input.read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
+ *outError = strerror(errno);
+ return false;
+ }
+
+ // If the PNG signature doesn't match, bail early.
+ if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+ *outError = "not a valid png file";
+ return false;
+ }
+
+ SourceLogger logger(source);
+ bool result = false;
+ png_structp readPtr = nullptr;
+ png_infop infoPtr = nullptr;
+ png_structp writePtr = nullptr;
+ png_infop writeInfoPtr = nullptr;
+ PngInfo pngInfo = {};
+
+ readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+ if (!readPtr) {
+ *outError = "failed to allocate read ptr";
+ goto bail;
+ }
+
+ infoPtr = png_create_info_struct(readPtr);
+ if (!infoPtr) {
+ *outError = "failed to allocate info ptr";
+ goto bail;
+ }
+
+ png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(&logger), nullptr, logWarning);
+
+ // Set the read function to read from std::istream.
+ png_set_read_fn(readPtr, (png_voidp)&input, readDataFromStream);
+
+ if (!readPng(readPtr, infoPtr, &pngInfo, outError)) {
+ goto bail;
+ }
+
+ if (util::stringEndsWith(source.path, ".9.png")) {
+ if (!do9Patch(&pngInfo, outError)) {
+ goto bail;
+ }
+ }
+
+ writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+ if (!writePtr) {
+ *outError = "failed to allocate write ptr";
+ goto bail;
+ }
+
+ writeInfoPtr = png_create_info_struct(writePtr);
+ if (!writeInfoPtr) {
+ *outError = "failed to allocate write info ptr";
+ goto bail;
+ }
+
+ png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
+
+ // Set the write function to write to std::ostream.
+ png_set_write_fn(writePtr, (png_voidp)&output, writeDataToStream, flushDataToStream);
+
+ if (!writePng(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger,
+ outError)) {
+ goto bail;
+ }
+
+ result = true;
+bail:
+ if (readPtr) {
+ png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
+ }
+
+ if (writePtr) {
+ png_destroy_write_struct(&writePtr, &writeInfoPtr);
+ }
+ return result;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Png.h b/tools/aapt2/Png.h
new file mode 100644
index 0000000..bc80754
--- /dev/null
+++ b/tools/aapt2/Png.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#ifndef AAPT_PNG_H
+#define AAPT_PNG_H
+
+#include "Source.h"
+
+#include <iostream>
+#include <string>
+
+namespace aapt {
+
+struct Png {
+ struct Options {
+ int grayScaleTolerance = 0;
+ };
+
+ bool process(const Source& source, std::istream& input, std::ostream& output,
+ const Options& options, std::string* outError);
+};
+
+} // namespace aapt
+
+#endif // AAPT_PNG_H
diff --git a/tools/aapt2/data/res/drawable/icon.png b/tools/aapt2/data/res/drawable/icon.png
new file mode 100644
index 0000000..4bff9b9
--- /dev/null
+++ b/tools/aapt2/data/res/drawable/icon.png
Binary files differ
diff --git a/tools/aapt2/data/res/drawable/test.9.png b/tools/aapt2/data/res/drawable/test.9.png
new file mode 100644
index 0000000..33daa11
--- /dev/null
+++ b/tools/aapt2/data/res/drawable/test.9.png
Binary files differ
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 72ee343..df76bc9 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -518,7 +518,7 @@
if m.name.startswith("create") and m.name.endswith("Intent"):
pass
else:
- error(clazz, m, "FW1", "Methods creating an Intent must be named createFooIntent()")
+ warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
def verify_helper_classes(clazz):
diff --git a/tools/data-binding/annotationprocessor/build.gradle b/tools/data-binding/annotationprocessor/build.gradle
new file mode 100644
index 0000000..a639abb
--- /dev/null
+++ b/tools/data-binding/annotationprocessor/build.gradle
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+apply plugin: 'java'
+apply plugin: 'maven'
+
+sourceCompatibility = 1.7
+targetCompatibility = 1.7
+
+buildscript {
+ repositories {
+ mavenLocal()
+ mavenCentral()
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+sourceSets {
+ main {
+ java {
+ srcDir 'src/main/java'
+ }
+ }
+}
+
+dependencies {
+ compile project(":baseLibrary")
+ compile project(":compiler")
+ compile 'commons-codec:commons-codec:1.10'
+}
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ pom.artifactId = 'annotationprocessor'
+ }
+ }
+}
diff --git a/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/AnnotationUtil.java b/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/AnnotationUtil.java
new file mode 100644
index 0000000..c82a976
--- /dev/null
+++ b/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/AnnotationUtil.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.annotationprocessor;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Element;
+
+class AnnotationUtil {
+
+ /**
+ * Returns only the elements that are annotated with the given class. For some reason
+ * RoundEnvironment is returning elements annotated by other annotations.
+ */
+ static List<Element> getElementsAnnotatedWith(RoundEnvironment roundEnv,
+ Class<? extends Annotation> annotationClass) {
+ ArrayList<Element> elements = new ArrayList<>();
+ for (Element element : roundEnv.getElementsAnnotatedWith(annotationClass)) {
+ if (element.getAnnotation(annotationClass) != null) {
+ elements.add(element);
+ }
+ }
+ return elements;
+ }
+}
diff --git a/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/BuildInfoUtil.java b/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/BuildInfoUtil.java
new file mode 100644
index 0000000..df525c1
--- /dev/null
+++ b/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/BuildInfoUtil.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.annotationprocessor;
+
+import com.google.common.base.Preconditions;
+
+import android.databinding.BindingBuildInfo;
+
+import java.lang.annotation.Annotation;
+
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Element;
+
+public class BuildInfoUtil {
+ private static BindingBuildInfo sCached;
+ public static BindingBuildInfo load(RoundEnvironment roundEnvironment) {
+ if (sCached == null) {
+ sCached = extractNotNull(roundEnvironment, BindingBuildInfo.class);
+ }
+ return sCached;
+ }
+
+ private static <T extends Annotation> T extractNotNull(RoundEnvironment roundEnv,
+ Class<T> annotationClass) {
+ T result = null;
+ for (Element element : roundEnv.getElementsAnnotatedWith(annotationClass)) {
+ final T info = element.getAnnotation(annotationClass);
+ if (info == null) {
+ continue; // It gets confused between BindingAppInfo and BinderBundle
+ }
+ Preconditions.checkState(result == null, "Should have only one %s",
+ annotationClass.getCanonicalName());
+ result = info;
+ }
+ return result;
+ }
+}
diff --git a/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/ProcessBindable.java b/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/ProcessBindable.java
new file mode 100644
index 0000000..d4582cb
--- /dev/null
+++ b/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/ProcessBindable.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.annotationprocessor;
+
+import com.google.common.base.Preconditions;
+
+import android.databinding.Bindable;
+import android.databinding.BindingBuildInfo;
+import android.databinding.tool.CompilerChef.BindableHolder;
+import android.databinding.tool.util.GenerationalClassUtil;
+import android.databinding.tool.util.L;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+
+// binding app info and library info are necessary to trigger this.
+@SupportedSourceVersion(SourceVersion.RELEASE_7)
+public class ProcessBindable extends ProcessDataBinding.ProcessingStep implements BindableHolder {
+ private static final String INTERMEDIATE_FILE_EXT = "-br.bin";
+ Intermediate mProperties;
+ HashMap<String, HashSet<String>> mLayoutVariables = new HashMap<>();
+
+ @Override
+ public boolean onHandleStep(RoundEnvironment roundEnv, ProcessingEnvironment processingEnv,
+ BindingBuildInfo buildInfo) {
+ if (mProperties == null) {
+ mProperties = new IntermediateV1(buildInfo.modulePackage());
+ mergeLayoutVariables();
+ mLayoutVariables.clear();
+ for (Element element : AnnotationUtil
+ .getElementsAnnotatedWith(roundEnv, Bindable.class)) {
+ Element enclosingElement = element.getEnclosingElement();
+ ElementKind kind = enclosingElement.getKind();
+ if (kind != ElementKind.CLASS && kind != ElementKind.INTERFACE) {
+ L.e("Bindable must be on a member field or method. The enclosing type is %s",
+ enclosingElement.getKind());
+ }
+ TypeElement enclosing = (TypeElement) enclosingElement;
+ String name = getPropertyName(element);
+ if (name != null) {
+ Preconditions
+ .checkNotNull(mProperties, "Must receive app / library info before "
+ + "Bindable fields.");
+ mProperties.addProperty(enclosing.getQualifiedName().toString(), name);
+ }
+ }
+ if (mProperties.hasValues()) {
+ GenerationalClassUtil.writeIntermediateFile(processingEnv,
+ mProperties.getPackage(),
+ createIntermediateFileName(mProperties.getPackage()), mProperties);
+ generateBRClasses(!buildInfo.isLibrary(), mProperties.getPackage());
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void addVariable(String variableName, String containingClassName) {
+ HashSet<String> variableNames = mLayoutVariables.get(containingClassName);
+ if (variableNames == null) {
+ variableNames = new HashSet<>();
+ mLayoutVariables.put(containingClassName, variableNames);
+ }
+ variableNames.add(variableName);
+ }
+
+ @Override
+ public void onProcessingOver(RoundEnvironment roundEnvironment,
+ ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
+ }
+
+ private String createIntermediateFileName(String appPkg) {
+ return appPkg + INTERMEDIATE_FILE_EXT;
+ }
+
+ private void generateBRClasses(boolean useFinalFields, String pkg) {
+ L.d("************* Generating BR file %s. use final: %s", pkg, useFinalFields);
+ HashSet<String> properties = new HashSet<>();
+ mProperties.captureProperties(properties);
+ List<Intermediate> previousIntermediates = loadPreviousBRFiles();
+ for (Intermediate intermediate : previousIntermediates) {
+ intermediate.captureProperties(properties);
+ }
+ writeBRClass(useFinalFields, pkg, properties);
+ if (useFinalFields) {
+ // generate BR for all previous packages
+ for (Intermediate intermediate : previousIntermediates) {
+ writeBRClass(true, intermediate.getPackage(),
+ properties);
+ }
+ }
+ }
+
+ private void writeBRClass(boolean useFinalFields, String pkg, HashSet<String> properties) {
+ ArrayList<String> sortedProperties = new ArrayList<String>();
+ sortedProperties.addAll(properties);
+ Collections.sort(sortedProperties);
+ StringBuilder out = new StringBuilder();
+ String modifier = "public static " + (useFinalFields ? "final" : "") + " int ";
+ out.append("package " + pkg + ";\n\n" +
+ "public class BR {\n" +
+ " " + modifier + "_all = 0;\n"
+ );
+ int id = 0;
+ for (String property : sortedProperties) {
+ id++;
+ out.append(" " + modifier + property + " = " + id + ";\n");
+ }
+ out.append(" public static int getId(String key) {\n");
+ out.append(" switch(key) {\n");
+ id = 0;
+ for (String property : sortedProperties) {
+ id++;
+ out.append(" case \"" + property + "\": return " + id + ";\n");
+ }
+ out.append(" }\n");
+ out.append(" return -1;\n");
+ out.append(" }");
+ out.append("}\n");
+
+ getWriter().writeToFile(pkg + ".BR", out.toString() );
+ }
+
+ private String getPropertyName(Element element) {
+ switch (element.getKind()) {
+ case FIELD:
+ return stripPrefixFromField((VariableElement) element);
+ case METHOD:
+ return stripPrefixFromMethod((ExecutableElement) element);
+ default:
+ L.e("@Bindable is not allowed on %s", element.getKind());
+ return null;
+ }
+ }
+
+ private static String stripPrefixFromField(VariableElement element) {
+ Name name = element.getSimpleName();
+ if (name.length() >= 2) {
+ char firstChar = name.charAt(0);
+ char secondChar = name.charAt(1);
+ if (name.length() > 2 && firstChar == 'm' && secondChar == '_') {
+ char thirdChar = name.charAt(2);
+ if (Character.isJavaIdentifierStart(thirdChar)) {
+ return "" + Character.toLowerCase(thirdChar) +
+ name.subSequence(3, name.length());
+ }
+ } else if ((firstChar == 'm' && Character.isUpperCase(secondChar)) ||
+ (firstChar == '_' && Character.isJavaIdentifierStart(secondChar))) {
+ return "" + Character.toLowerCase(secondChar) + name.subSequence(2, name.length());
+ }
+ }
+ return name.toString();
+ }
+
+ private String stripPrefixFromMethod(ExecutableElement element) {
+ Name name = element.getSimpleName();
+ CharSequence propertyName;
+ if (isGetter(element) || isSetter(element)) {
+ propertyName = name.subSequence(3, name.length());
+ } else if (isBooleanGetter(element)) {
+ propertyName = name.subSequence(2, name.length());
+ } else {
+ L.e("@Bindable associated with method must follow JavaBeans convention %s", element);
+ return null;
+ }
+ char firstChar = propertyName.charAt(0);
+ return "" + Character.toLowerCase(firstChar) +
+ propertyName.subSequence(1, propertyName.length());
+ }
+
+ private void mergeLayoutVariables() {
+ for (String containingClass : mLayoutVariables.keySet()) {
+ for (String variable : mLayoutVariables.get(containingClass)) {
+ mProperties.addProperty(containingClass, variable);
+ }
+ }
+ }
+
+ private static boolean prefixes(CharSequence sequence, String prefix) {
+ boolean prefixes = false;
+ if (sequence.length() > prefix.length()) {
+ int count = prefix.length();
+ prefixes = true;
+ for (int i = 0; i < count; i++) {
+ if (sequence.charAt(i) != prefix.charAt(i)) {
+ prefixes = false;
+ break;
+ }
+ }
+ }
+ return prefixes;
+ }
+
+ private static boolean isGetter(ExecutableElement element) {
+ Name name = element.getSimpleName();
+ return prefixes(name, "get") &&
+ Character.isJavaIdentifierStart(name.charAt(3)) &&
+ element.getParameters().isEmpty() &&
+ element.getReturnType().getKind() != TypeKind.VOID;
+ }
+
+ private static boolean isSetter(ExecutableElement element) {
+ Name name = element.getSimpleName();
+ return prefixes(name, "set") &&
+ Character.isJavaIdentifierStart(name.charAt(3)) &&
+ element.getParameters().size() == 1 &&
+ element.getReturnType().getKind() == TypeKind.VOID;
+ }
+
+ private static boolean isBooleanGetter(ExecutableElement element) {
+ Name name = element.getSimpleName();
+ return prefixes(name, "is") &&
+ Character.isJavaIdentifierStart(name.charAt(2)) &&
+ element.getParameters().isEmpty() &&
+ element.getReturnType().getKind() == TypeKind.BOOLEAN;
+ }
+
+ private List<Intermediate> loadPreviousBRFiles() {
+ return GenerationalClassUtil
+ .loadObjects(getClass().getClassLoader(),
+ new GenerationalClassUtil.ExtensionFilter(INTERMEDIATE_FILE_EXT));
+ }
+
+ private interface Intermediate extends Serializable {
+
+ void captureProperties(Set<String> properties);
+
+ void addProperty(String className, String propertyName);
+
+ boolean hasValues();
+
+ String getPackage();
+ }
+
+ private static class IntermediateV1 implements Serializable, Intermediate {
+
+ private static final long serialVersionUID = 2L;
+
+ private String mPackage;
+ private final HashMap<String, HashSet<String>> mProperties = new HashMap<>();
+
+ public IntermediateV1(String aPackage) {
+ mPackage = aPackage;
+ }
+
+ @Override
+ public void captureProperties(Set<String> properties) {
+ for (HashSet<String> propertySet : mProperties.values()) {
+ properties.addAll(propertySet);
+ }
+ }
+
+ @Override
+ public void addProperty(String className, String propertyName) {
+ HashSet<String> properties = mProperties.get(className);
+ if (properties == null) {
+ properties = new HashSet<>();
+ mProperties.put(className, properties);
+ }
+ properties.add(propertyName);
+ }
+
+ @Override
+ public boolean hasValues() {
+ return !mProperties.isEmpty();
+ }
+
+ @Override
+ public String getPackage() {
+ return mPackage;
+ }
+ }
+}
diff --git a/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java b/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java
new file mode 100644
index 0000000..944cc20
--- /dev/null
+++ b/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.annotationprocessor;
+
+import android.databinding.BindingBuildInfo;
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.writer.AnnotationJavaFileWriter;
+import android.databinding.tool.writer.JavaFileWriter;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+
+@SupportedAnnotationTypes({
+ "android.databinding.BindingAdapter",
+ "android.databinding.Untaggable",
+ "android.databinding.BindingMethods",
+ "android.databinding.BindingConversion",
+ "android.databinding.BindingBuildInfo"}
+)
+@SupportedSourceVersion(SourceVersion.RELEASE_7)
+/**
+ * Parent annotation processor that dispatches sub steps to ensure execution order.
+ * Use initProcessingSteps to add a new step.
+ */
+public class ProcessDataBinding extends AbstractProcessor {
+ private List<ProcessingStep> mProcessingSteps;
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (mProcessingSteps == null) {
+ initProcessingSteps();
+ }
+ final BindingBuildInfo buildInfo = BuildInfoUtil.load(roundEnv);
+ if (buildInfo == null) {
+ return false;
+ }
+ boolean done = true;
+ for (ProcessingStep step : mProcessingSteps) {
+ done = step.runStep(roundEnv, processingEnv, buildInfo) && done;
+ }
+ if (roundEnv.processingOver()) {
+ for (ProcessingStep step : mProcessingSteps) {
+ step.onProcessingOver(roundEnv, processingEnv, buildInfo);
+ }
+ }
+ return done;
+ }
+
+ private void initProcessingSteps() {
+ ProcessBindable processBindable = new ProcessBindable();
+ mProcessingSteps = Arrays.asList(
+ new ProcessMethodAdapters(),
+ new ProcessExpressions(processBindable),
+ processBindable
+ );
+ AnnotationJavaFileWriter javaFileWriter = new AnnotationJavaFileWriter(processingEnv);
+ for (ProcessingStep step : mProcessingSteps) {
+ step.mJavaFileWriter = javaFileWriter;
+ }
+ }
+
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+ ModelAnalyzer.setProcessingEnvironment(processingEnv);
+ }
+
+ /**
+ * To ensure execution order and binding build information, we use processing steps.
+ */
+ public abstract static class ProcessingStep {
+ private boolean mDone;
+ private JavaFileWriter mJavaFileWriter;
+
+ protected JavaFileWriter getWriter() {
+ return mJavaFileWriter;
+ }
+
+ private boolean runStep(RoundEnvironment roundEnvironment,
+ ProcessingEnvironment processingEnvironment,
+ BindingBuildInfo buildInfo) {
+ if (mDone) {
+ return true;
+ }
+ mDone = onHandleStep(roundEnvironment, processingEnvironment, buildInfo);
+ return mDone;
+ }
+
+ /**
+ * Invoked in each annotation processing step.
+ *
+ * @return True if it is done and should never be invoked again.
+ */
+ abstract public boolean onHandleStep(RoundEnvironment roundEnvironment,
+ ProcessingEnvironment processingEnvironment,
+ BindingBuildInfo buildInfo);
+
+ /**
+ * Invoked when processing is done. A good place to generate the output if the
+ * processor requires multiple steps.
+ */
+ abstract public void onProcessingOver(RoundEnvironment roundEnvironment,
+ ProcessingEnvironment processingEnvironment,
+ BindingBuildInfo buildInfo);
+ }
+}
diff --git a/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java b/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java
new file mode 100644
index 0000000..d62715a
--- /dev/null
+++ b/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/ProcessExpressions.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.annotationprocessor;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+
+import android.databinding.BindingBuildInfo;
+import android.databinding.tool.CompilerChef;
+import android.databinding.tool.reflection.SdkUtil;
+import android.databinding.tool.store.ResourceBundle;
+import android.databinding.tool.util.GenerationalClassUtil;
+import android.databinding.tool.util.L;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+
+public class ProcessExpressions extends ProcessDataBinding.ProcessingStep {
+
+ private static final String LAYOUT_INFO_FILE_SUFFIX = "-layoutinfo.bin";
+
+ private final ProcessBindable mProcessBindable;
+
+ public ProcessExpressions(ProcessBindable processBindable) {
+ mProcessBindable = processBindable;
+ }
+
+
+ @Override
+ public boolean onHandleStep(RoundEnvironment roundEnvironment,
+ ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
+ ResourceBundle resourceBundle;
+ SdkUtil.initialize(buildInfo.minSdk(), new File(buildInfo.sdkRoot()));
+ resourceBundle = new ResourceBundle(buildInfo.modulePackage());
+ List<Intermediate> intermediateList =
+ GenerationalClassUtil.loadObjects(getClass().getClassLoader(),
+ new GenerationalClassUtil.ExtensionFilter(LAYOUT_INFO_FILE_SUFFIX));
+ IntermediateV1 mine = createIntermediateFromLayouts(buildInfo.layoutInfoDir());
+ if (mine != null) {
+ mine.removeOverridden(intermediateList);
+ intermediateList.add(mine);
+ saveIntermediate(processingEnvironment, buildInfo, mine);
+ }
+ // generate them here so that bindable parser can read
+ try {
+ generateBinders(resourceBundle, buildInfo, intermediateList);
+ } catch (Throwable t) {
+ L.e(t, "cannot generate view binders");
+ }
+ return true;
+ }
+
+ private void saveIntermediate(ProcessingEnvironment processingEnvironment,
+ BindingBuildInfo buildInfo, IntermediateV1 intermediate) {
+ GenerationalClassUtil.writeIntermediateFile(processingEnvironment,
+ buildInfo.modulePackage(), buildInfo.modulePackage() + LAYOUT_INFO_FILE_SUFFIX,
+ intermediate);
+ }
+
+ @Override
+ public void onProcessingOver(RoundEnvironment roundEnvironment,
+ ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
+ }
+
+ private void generateBinders(ResourceBundle resourceBundle, BindingBuildInfo buildInfo,
+ List<Intermediate> intermediates)
+ throws Throwable {
+ for (Intermediate intermediate : intermediates) {
+ intermediate.appendTo(resourceBundle);
+ }
+ writeResourceBundle(resourceBundle, buildInfo.isLibrary(), buildInfo.minSdk());
+ }
+
+ private IntermediateV1 createIntermediateFromLayouts(String layoutInfoFolderPath) {
+ final File layoutInfoFolder = new File(layoutInfoFolderPath);
+ if (!layoutInfoFolder.isDirectory()) {
+ L.d("layout info folder does not exist, skipping for %s", layoutInfoFolderPath);
+ return null;
+ }
+ IntermediateV1 result = new IntermediateV1();
+ for (File layoutFile : layoutInfoFolder.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.endsWith(".xml");
+ }
+ })) {
+ try {
+ result.addEntry(layoutFile.getName(), FileUtils.readFileToString(layoutFile));
+ } catch (IOException e) {
+ L.e(e, "cannot load layout file information. Try a clean build");
+ }
+ }
+ return result;
+ }
+
+ private void writeResourceBundle(ResourceBundle resourceBundle, boolean forLibraryModule,
+ int minSdk)
+ throws JAXBException {
+ CompilerChef compilerChef = CompilerChef.createChef(resourceBundle, getWriter());
+ if (compilerChef.hasAnythingToGenerate()) {
+ compilerChef.addBRVariables(mProcessBindable);
+ compilerChef.writeViewBinderInterfaces(forLibraryModule);
+ if (!forLibraryModule) {
+ compilerChef.writeDbrFile(minSdk);
+ compilerChef.writeViewBinders();
+ }
+ }
+ }
+
+ public static interface Intermediate extends Serializable {
+
+ Intermediate upgrade();
+
+ public void appendTo(ResourceBundle resourceBundle) throws Throwable;
+ }
+
+ public static class IntermediateV1 implements Intermediate {
+
+ transient Unmarshaller mUnmarshaller;
+
+ // name to xml content map
+ Map<String, String> mLayoutInfoMap = new HashMap<>();
+
+ @Override
+ public Intermediate upgrade() {
+ return this;
+ }
+
+ @Override
+ public void appendTo(ResourceBundle resourceBundle) throws JAXBException {
+ if (mUnmarshaller == null) {
+ JAXBContext context = JAXBContext
+ .newInstance(ResourceBundle.LayoutFileBundle.class);
+ mUnmarshaller = context.createUnmarshaller();
+ }
+ for (String content : mLayoutInfoMap.values()) {
+ final InputStream is = IOUtils.toInputStream(content);
+ try {
+ final ResourceBundle.LayoutFileBundle bundle
+ = (ResourceBundle.LayoutFileBundle) mUnmarshaller.unmarshal(is);
+ resourceBundle.addLayoutBundle(bundle);
+ L.d("loaded layout info file %s", bundle);
+ } finally {
+ IOUtils.closeQuietly(is);
+ }
+ }
+ }
+
+ public void addEntry(String name, String contents) {
+ mLayoutInfoMap.put(name, contents);
+ }
+
+ public void removeOverridden(List<Intermediate> existing) {
+ // this is the way we get rid of files that are copied from previous modules
+ // it is important to do this before saving the intermediate file
+ for (Intermediate old : existing) {
+ if (old instanceof IntermediateV1) {
+ IntermediateV1 other = (IntermediateV1) old;
+ for (String key : other.mLayoutInfoMap.keySet()) {
+ // TODO we should consider the original file as the key here
+ // but aapt probably cannot provide that information
+ if (mLayoutInfoMap.remove(key) != null) {
+ L.d("removing %s from bundle because it came from another module", key);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/ProcessMethodAdapters.java b/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/ProcessMethodAdapters.java
new file mode 100644
index 0000000..98da839
--- /dev/null
+++ b/tools/data-binding/annotationprocessor/src/main/java/android/databinding/annotationprocessor/ProcessMethodAdapters.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.annotationprocessor;
+
+import com.google.common.base.Preconditions;
+
+import android.databinding.BindingAdapter;
+import android.databinding.BindingBuildInfo;
+import android.databinding.BindingConversion;
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+import android.databinding.Untaggable;
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.store.SetterStore;
+import android.databinding.tool.util.L;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeKind;
+import javax.tools.Diagnostic;
+
+public class ProcessMethodAdapters extends ProcessDataBinding.ProcessingStep {
+ public ProcessMethodAdapters() {
+ }
+
+ @Override
+ public boolean onHandleStep(RoundEnvironment roundEnv,
+ ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
+ L.d("processing adapters");
+ final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
+ Preconditions.checkNotNull(modelAnalyzer, "Model analyzer should be"
+ + " initialized first");
+ SetterStore store = SetterStore.get(modelAnalyzer);
+ clearIncrementalClasses(roundEnv, store);
+
+ addBindingAdapters(roundEnv, processingEnvironment, store);
+ addRenamed(roundEnv, processingEnvironment, store);
+ addConversions(roundEnv, processingEnvironment, store);
+ addUntaggable(roundEnv, processingEnvironment, store);
+
+ try {
+ store.write(buildInfo.modulePackage(), processingEnvironment);
+ } catch (IOException e) {
+ L.e(e, "Could not write BindingAdapter intermediate file.");
+ }
+ return true;
+ }
+
+ @Override
+ public void onProcessingOver(RoundEnvironment roundEnvironment,
+ ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
+
+ }
+
+ private void addBindingAdapters(RoundEnvironment roundEnv, ProcessingEnvironment
+ processingEnv, SetterStore store) {
+ for (Element element : AnnotationUtil
+ .getElementsAnnotatedWith(roundEnv, BindingAdapter.class)) {
+ if (element.getKind() != ElementKind.METHOD ||
+ !element.getModifiers().contains(Modifier.STATIC) ||
+ !element.getModifiers().contains(Modifier.PUBLIC)) {
+ L.e("@BindingAdapter on invalid element: %s", element);
+ continue;
+ }
+ BindingAdapter bindingAdapter = element.getAnnotation(BindingAdapter.class);
+
+ ExecutableElement executableElement = (ExecutableElement) element;
+ List<? extends VariableElement> parameters = executableElement.getParameters();
+ if (parameters.size() != 2) {
+ L.e("@BindingAdapter does not take two parameters: %s",element);
+ continue;
+ }
+ try {
+ L.d("------------------ @BindingAdapter for %s", element);
+ store.addBindingAdapter(bindingAdapter.value(), executableElement);
+ } catch (IllegalArgumentException e) {
+ L.e(e, "@BindingAdapter for duplicate View and parameter type: %s", element);
+ }
+ }
+ }
+
+ private void addRenamed(RoundEnvironment roundEnv, ProcessingEnvironment processingEnv,
+ SetterStore store) {
+ for (Element element : AnnotationUtil
+ .getElementsAnnotatedWith(roundEnv, BindingMethods.class)) {
+ BindingMethods bindingMethods = element.getAnnotation(BindingMethods.class);
+ for (BindingMethod bindingMethod : bindingMethods.value()) {
+ store.addRenamedMethod(bindingMethod.attribute(),
+ bindingMethod.type(), bindingMethod.method(), (TypeElement) element);
+ }
+ }
+ }
+
+ private void addConversions(RoundEnvironment roundEnv,
+ ProcessingEnvironment processingEnv, SetterStore store) {
+ for (Element element : AnnotationUtil
+ .getElementsAnnotatedWith(roundEnv, BindingConversion.class)) {
+ if (element.getKind() != ElementKind.METHOD ||
+ !element.getModifiers().contains(Modifier.STATIC) ||
+ !element.getModifiers().contains(Modifier.PUBLIC)) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
+ "@BindingConversion is only allowed on public static methods: " + element);
+ continue;
+ }
+
+ ExecutableElement executableElement = (ExecutableElement) element;
+ if (executableElement.getParameters().size() != 1) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
+ "@BindingConversion method should have one parameter: " + element);
+ continue;
+ }
+ if (executableElement.getReturnType().getKind() == TypeKind.VOID) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
+ "@BindingConversion method must return a value: " + element);
+ continue;
+ }
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
+ "added conversion: " + element);
+ store.addConversionMethod(executableElement);
+ }
+ }
+
+ private void addUntaggable(RoundEnvironment roundEnv,
+ ProcessingEnvironment processingEnv, SetterStore store) {
+ for (Element element : AnnotationUtil.getElementsAnnotatedWith(roundEnv, Untaggable.class)) {
+ Untaggable untaggable = element.getAnnotation(Untaggable.class);
+ store.addUntaggableTypes(untaggable.value(), (TypeElement) element);
+ }
+ }
+
+ private void clearIncrementalClasses(RoundEnvironment roundEnv, SetterStore store) {
+ HashSet<String> classes = new HashSet<>();
+
+ for (Element element : AnnotationUtil
+ .getElementsAnnotatedWith(roundEnv, BindingAdapter.class)) {
+ TypeElement containingClass = (TypeElement) element.getEnclosingElement();
+ classes.add(containingClass.getQualifiedName().toString());
+ }
+ for (Element element : AnnotationUtil
+ .getElementsAnnotatedWith(roundEnv, BindingMethods.class)) {
+ classes.add(((TypeElement) element).getQualifiedName().toString());
+ }
+ for (Element element : AnnotationUtil
+ .getElementsAnnotatedWith(roundEnv, BindingConversion.class)) {
+ classes.add(((TypeElement) element.getEnclosingElement()).getQualifiedName().
+ toString());
+ }
+ for (Element element : AnnotationUtil.getElementsAnnotatedWith(roundEnv, Untaggable.class)) {
+ classes.add(((TypeElement) element).getQualifiedName().toString());
+ }
+ store.clear(classes);
+ }
+
+}
diff --git a/tools/data-binding/annotationprocessor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/tools/data-binding/annotationprocessor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..482452e
--- /dev/null
+++ b/tools/data-binding/annotationprocessor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+android.databinding.annotationprocessor.ProcessDataBinding
\ No newline at end of file
diff --git a/tools/data-binding/baseLibrary/build.gradle b/tools/data-binding/baseLibrary/build.gradle
new file mode 100644
index 0000000..ecb7205
--- /dev/null
+++ b/tools/data-binding/baseLibrary/build.gradle
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+apply plugin: 'java'
+apply plugin: 'maven'
+apply plugin: 'application'
+
+sourceCompatibility = config.javaTargetCompatibility
+targetCompatibility = config.javaSourceCompatibility
+
+buildscript {
+ repositories {
+ mavenLocal()
+ mavenCentral()
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+sourceSets {
+ main {
+ java {
+ srcDir 'src/main/java'
+ }
+ }
+ test {
+ java {
+ srcDir 'src/test/java'
+ }
+ }
+}
+
+dependencies {
+ testCompile 'junit:junit:4.11'
+}
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ pom.artifactId = 'baseLibrary'
+ }
+ }
+}
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/databinding/Bindable.java b/tools/data-binding/baseLibrary/src/main/java/android/databinding/Bindable.java
new file mode 100644
index 0000000..3dcebdd
--- /dev/null
+++ b/tools/data-binding/baseLibrary/src/main/java/android/databinding/Bindable.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.FIELD, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME) // this is necessary for java analyzer to work
+public @interface Bindable {
+}
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingAdapter.java b/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingAdapter.java
new file mode 100644
index 0000000..3d50ed3
--- /dev/null
+++ b/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingAdapter.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+public @interface BindingAdapter {
+ String value();
+}
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingBuildInfo.java b/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingBuildInfo.java
new file mode 100644
index 0000000..cbe1e99
--- /dev/null
+++ b/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingBuildInfo.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface BindingBuildInfo {
+ String buildId();
+ String modulePackage();
+ String sdkRoot();
+ int minSdk();
+
+ /**
+ * The folder that includes xml files which are exported by aapt or gradle plugin from layout files
+ */
+ String layoutInfoDir();
+ boolean isLibrary();
+}
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingConversion.java b/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingConversion.java
new file mode 100644
index 0000000..9942f5c
--- /dev/null
+++ b/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingConversion.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface BindingConversion {
+}
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingMethod.java b/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingMethod.java
new file mode 100644
index 0000000..6384409
--- /dev/null
+++ b/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingMethod.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+public @interface BindingMethod {
+ String type();
+ String attribute();
+ String method();
+}
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingMethods.java b/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingMethods.java
new file mode 100644
index 0000000..a3d39f8
--- /dev/null
+++ b/tools/data-binding/baseLibrary/src/main/java/android/databinding/BindingMethods.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface BindingMethods {
+ BindingMethod[] value();
+}
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/databinding/CallbackRegistry.java b/tools/data-binding/baseLibrary/src/main/java/android/databinding/CallbackRegistry.java
new file mode 100644
index 0000000..002692f
--- /dev/null
+++ b/tools/data-binding/baseLibrary/src/main/java/android/databinding/CallbackRegistry.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tracks callbacks for the event. This class supports reentrant modification
+ * of the callbacks during notification without adversely disrupting notifications.
+ * A common pattern for callbacks is to receive a notification and then remove
+ * themselves. This class handles this behavior with constant memory under
+ * most circumstances.
+ *
+ * <p>A subclass of {@link CallbackRegistry.NotifierCallback} must be passed to
+ * the constructor to define how notifications should be called. That implementation
+ * does the actual notification on the listener.</p>
+ *
+ * <p>This class supports only callbacks with at most two parameters.
+ * Typically, these are the notification originator and a parameter, but these may
+ * be used as required. If more than two parameters are required or primitive types
+ * must be used, <code>A</code> should be some kind of containing structure that
+ * the subclass may reuse between notifications.</p>
+ *
+ * @param <C> The callback type.
+ * @param <T> The notification sender type. Typically this is the containing class.
+ * @param <A> Opaque argument used to pass additional data beyond an int.
+ */
+public class CallbackRegistry<C, T, A> implements Cloneable {
+ private static final String TAG = "CallbackRegistry";
+
+ /** An ordered collection of listeners waiting to be notified. */
+ private List<C> mCallbacks = new ArrayList<C>();
+
+ /**
+ * A bit flag for the first 64 listeners that are removed during notification.
+ * The lowest significant bit corresponds to the 0th index into mCallbacks.
+ * For a small number of callbacks, no additional array of objects needs to
+ * be allocated.
+ */
+ private long mFirst64Removed = 0x0;
+
+ /**
+ * Bit flags for the remaining callbacks that are removed during notification.
+ * When there are more than 64 callbacks and one is marked for removal, a dynamic
+ * array of bits are allocated for the callbacks.
+ */
+ private long[] mRemainderRemoved;
+
+ /** The recursion level of the notification */
+ private int mNotificationLevel;
+
+ /** The notification mechanism for notifying an event. */
+ private final NotifierCallback<C, T, A> mNotifier;
+
+ /**
+ * Creates an EventRegistry that notifies the event with notifier.
+ * @param notifier The class to use to notify events.
+ */
+ public CallbackRegistry(NotifierCallback<C, T, A> notifier) {
+ mNotifier = notifier;
+ }
+
+ /**
+ * Notify all callbacks.
+ *
+ * @param sender The originator. This is an opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg2 An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ */
+ public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
+ mNotificationLevel++;
+ notifyRecurse(sender, arg, arg2);
+ mNotificationLevel--;
+ if (mNotificationLevel == 0) {
+ if (mRemainderRemoved != null) {
+ for (int i = mRemainderRemoved.length - 1; i >= 0; i--) {
+ final long removedBits = mRemainderRemoved[i];
+ if (removedBits != 0) {
+ removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits);
+ mRemainderRemoved[i] = 0;
+ }
+ }
+ }
+ if (mFirst64Removed != 0) {
+ removeRemovedCallbacks(0, mFirst64Removed);
+ mFirst64Removed = 0;
+ }
+ }
+ }
+
+ /**
+ * Notify up to the first Long.SIZE callbacks that don't have a bit set in <code>removed</code>.
+ *
+ * @param sender The originator. This is an opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg2 An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ */
+ private void notifyFirst64(T sender, int arg, A arg2) {
+ final int maxNotified = Math.min(Long.SIZE, mCallbacks.size());
+ notifyCallbacks(sender, arg, arg2, 0, maxNotified, mFirst64Removed);
+ }
+
+ /**
+ * Notify all callbacks using a recursive algorithm to avoid allocating on the heap.
+ * This part captures the callbacks beyond Long.SIZE that have no bits allocated for
+ * removal before it recurses into {@link #notifyRemainder(Object, int, A, int)}.
+ *
+ * <p>Recursion is used to avoid allocating temporary state on the heap.</p>
+ *
+ * @param sender The originator. This is an opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg2 An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ */
+ private void notifyRecurse(T sender, int arg, A arg2) {
+ final int callbackCount = mCallbacks.size();
+ final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1;
+
+ // Now we've got all callbakcs that have no mRemainderRemoved value, so notify the
+ // others.
+ notifyRemainder(sender, arg, arg2, remainderIndex);
+
+ // notifyRemainder notifies all at maxIndex, so we'd normally start at maxIndex + 1
+ // However, we must also keep track of those in mFirst64Removed, so we add 2 instead:
+ final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE;
+
+ // The remaining have no bit set
+ notifyCallbacks(sender, arg, arg2, startCallbackIndex, callbackCount, 0);
+ }
+
+ /**
+ * Notify callbacks that have mRemainderRemoved bits set for remainderIndex. If
+ * remainderIndex is -1, the first 64 will be notified instead.
+ *
+ * @param sender The originator. This is an opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg2 An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param remainderIndex The index into mRemainderRemoved that should be notified.
+ */
+ private void notifyRemainder(T sender, int arg, A arg2, int remainderIndex) {
+ if (remainderIndex < 0) {
+ notifyFirst64(sender, arg, arg2);
+ } else {
+ final long bits = mRemainderRemoved[remainderIndex];
+ final int startIndex = (remainderIndex + 1) * Long.SIZE;
+ final int endIndex = Math.min(mCallbacks.size(), startIndex + Long.SIZE);
+ notifyRemainder(sender, arg, arg2, remainderIndex - 1);
+ notifyCallbacks(sender, arg, arg2, startIndex, endIndex, bits);
+ }
+ }
+
+ /**
+ * Notify callbacks from startIndex to endIndex, using bits as the bit status
+ * for whether they have been removed or not. bits should be from mRemainderRemoved or
+ * mFirst64Removed. bits set to 0 indicates that all callbacks from startIndex to
+ * endIndex should be notified.
+ *
+ * @param sender The originator. This is an opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param arg2 An opaque parameter passed to
+ * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)}
+ * @param startIndex The index into the mCallbacks to start notifying.
+ * @param endIndex One past the last index into mCallbacks to notify.
+ * @param bits A bit field indicating which callbacks have been removed and shouldn't
+ * be notified.
+ */
+ private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
+ final int endIndex, final long bits) {
+ long bitMask = 1;
+ for (int i = startIndex; i < endIndex; i++) {
+ if ((bits & bitMask) == 0) {
+ mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
+ }
+ bitMask <<= 1;
+ }
+ }
+
+ /**
+ * Add a callback to be notified. If the callback is already in the list, another won't
+ * be added. This does not affect current notifications.
+ * @param callback The callback to add.
+ */
+ public synchronized void add(C callback) {
+ int index = mCallbacks.lastIndexOf(callback);
+ if (index < 0 || isRemoved(index)) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ /**
+ * Returns true if the callback at index has been marked for removal.
+ *
+ * @param index The index into mCallbacks to check.
+ * @return true if the callback at index has been marked for removal.
+ */
+ private boolean isRemoved(int index) {
+ if (index < Long.SIZE) {
+ // It is in the first 64 callbacks, just check the bit.
+ final long bitMask = 1L << index;
+ return (mFirst64Removed & bitMask) != 0;
+ } else if (mRemainderRemoved == null) {
+ // It is after the first 64 callbacks, but nothing else was marked for removal.
+ return false;
+ } else {
+ final int maskIndex = (index / Long.SIZE) - 1;
+ if (maskIndex >= mRemainderRemoved.length) {
+ // There are some items in mRemainderRemoved, but nothing at the given index.
+ return false;
+ } else {
+ // There is something marked for removal, so we have to check the bit.
+ final long bits = mRemainderRemoved[maskIndex];
+ final long bitMask = 1L << (index % Long.SIZE);
+ return (bits & bitMask) != 0;
+ }
+ }
+ }
+
+ /**
+ * Removes callbacks from startIndex to startIndex + Long.SIZE, based
+ * on the bits set in removed.
+ * @param startIndex The index into the mCallbacks to start removing callbacks.
+ * @param removed The bits indicating removal, where each bit is set for one callback
+ * to be removed.
+ */
+ private void removeRemovedCallbacks(int startIndex, long removed) {
+ // The naive approach should be fine. There may be a better bit-twiddling approach.
+ final int endIndex = startIndex + Long.SIZE;
+
+ long bitMask = 1L << (Long.SIZE - 1);
+ for (int i = endIndex - 1; i >= startIndex; i--) {
+ if ((removed & bitMask) != 0) {
+ mCallbacks.remove(i);
+ }
+ bitMask >>>= 1;
+ }
+ }
+
+ /**
+ * Remove a callback. This callback won't be notified after this call completes.
+ * @param callback The callback to remove.
+ */
+ public synchronized void remove(C callback) {
+ if (mNotificationLevel == 0) {
+ mCallbacks.remove(callback);
+ } else {
+ int index = mCallbacks.lastIndexOf(callback);
+ if (index >= 0) {
+ setRemovalBit(index);
+ }
+ }
+ }
+
+ private void setRemovalBit(int index) {
+ if (index < Long.SIZE) {
+ // It is in the first 64 callbacks, just check the bit.
+ final long bitMask = 1L << index;
+ mFirst64Removed |= bitMask;
+ } else {
+ final int remainderIndex = (index / Long.SIZE) - 1;
+ if (mRemainderRemoved == null) {
+ mRemainderRemoved = new long[mCallbacks.size() / Long.SIZE];
+ } else if (mRemainderRemoved.length < remainderIndex) {
+ // need to make it bigger
+ long[] newRemainders = new long[mCallbacks.size() / Long.SIZE];
+ System.arraycopy(mRemainderRemoved, 0, newRemainders, 0, mRemainderRemoved.length);
+ mRemainderRemoved = newRemainders;
+ }
+ final long bitMask = 1L << (index % Long.SIZE);
+ mRemainderRemoved[remainderIndex] |= bitMask;
+ }
+ }
+
+ /*
+ private void clearRemovalBit(int index) {
+ if (index < Long.SIZE) {
+ // It is in the first 64 callbacks, just check the bit.
+ final long bitMask = 1L << index;
+ mFirst64Removed &= ~bitMask;
+ } else if (mRemainderRemoved != null) {
+ final int maskIndex = (index / Long.SIZE) - 1;
+ if (maskIndex < mRemainderRemoved.length) {
+ // There is something marked for removal, so we have to check the bit.
+ final long bitMask = 1L << (index % Long.SIZE);
+ mRemainderRemoved[maskIndex] &= ~bitMask;
+ }
+ }
+ }
+ */
+
+ /**
+ * Makes a copy of the registered callbacks and returns it.
+ *
+ * @return a copy of the registered callbacks.
+ */
+ public synchronized ArrayList<C> copyListeners() {
+ ArrayList<C> callbacks = new ArrayList<C>(mCallbacks.size());
+ int numListeners = mCallbacks.size();
+ for (int i = 0; i < numListeners; i++) {
+ if (!isRemoved(i)) {
+ callbacks.add(mCallbacks.get(i));
+ }
+ }
+ return callbacks;
+ }
+
+ /**
+ * Returns true if there are no registered callbacks or false otherwise.
+ *
+ * @return true if there are no registered callbacks or false otherwise.
+ */
+ public synchronized boolean isEmpty() {
+ if (mCallbacks.isEmpty()) {
+ return true;
+ } else if (mNotificationLevel == 0) {
+ return false;
+ } else {
+ int numListeners = mCallbacks.size();
+ for (int i = 0; i < numListeners; i++) {
+ if (!isRemoved(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Removes all callbacks from the list.
+ */
+ public synchronized void clear() {
+ if (mNotificationLevel == 0) {
+ mCallbacks.clear();
+ } else if (!mCallbacks.isEmpty()) {
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ setRemovalBit(i);
+ }
+ }
+ }
+
+ public synchronized CallbackRegistry<C, T, A> clone() {
+ CallbackRegistry<C, T, A> clone = null;
+ try {
+ clone = (CallbackRegistry<C, T, A>) super.clone();
+ clone.mFirst64Removed = 0;
+ clone.mRemainderRemoved = null;
+ clone.mNotificationLevel = 0;
+ clone.mCallbacks = new ArrayList<C>();
+ final int numListeners = mCallbacks.size();
+ for (int i = 0; i < numListeners; i++) {
+ if (!isRemoved(i)) {
+ clone.mCallbacks.add(mCallbacks.get(i));
+ }
+ }
+ } catch (CloneNotSupportedException e) {
+ e.printStackTrace();
+ }
+ return clone;
+ }
+
+ /**
+ * Class used to notify events from CallbackRegistry.
+ *
+ * @param <C> The callback type.
+ * @param <T> The notification sender type. Typically this is the containing class.
+ * @param <A> An opaque argument to pass to the notifier
+ */
+ public abstract static class NotifierCallback<C, T, A> {
+ /**
+ * Used to notify the callback.
+ *
+ * @param callback The callback to notify.
+ * @param sender The opaque sender object.
+ * @param arg The opaque notification parameter.
+ * @param arg2 An opaque argument passed in
+ * {@link CallbackRegistry#notifyCallbacks}
+ * @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback)
+ */
+ public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2);
+ }
+}
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/databinding/Observable.java b/tools/data-binding/baseLibrary/src/main/java/android/databinding/Observable.java
new file mode 100644
index 0000000..caca167
--- /dev/null
+++ b/tools/data-binding/baseLibrary/src/main/java/android/databinding/Observable.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding;
+
+public interface Observable {
+
+ public void addOnPropertyChangedListener(OnPropertyChangedListener listener);
+
+ public void removeOnPropertyChangedListener(OnPropertyChangedListener listener);
+}
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/databinding/ObservableList.java b/tools/data-binding/baseLibrary/src/main/java/android/databinding/ObservableList.java
new file mode 100644
index 0000000..3b82cf1
--- /dev/null
+++ b/tools/data-binding/baseLibrary/src/main/java/android/databinding/ObservableList.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+import java.util.List;
+
+public interface ObservableList<T> extends List<T> {
+ void addOnListChangedListener(OnListChangedListener listener);
+ void removeOnListChangedListener(OnListChangedListener listener);
+}
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/databinding/ObservableMap.java b/tools/data-binding/baseLibrary/src/main/java/android/databinding/ObservableMap.java
new file mode 100644
index 0000000..9240c48
--- /dev/null
+++ b/tools/data-binding/baseLibrary/src/main/java/android/databinding/ObservableMap.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+import java.util.Map;
+
+public interface ObservableMap<K, V> extends Map<K, V> {
+ void addOnMapChangedListener(OnMapChangedListener<? extends ObservableMap<K, V>, K> listener);
+ void removeOnMapChangedListener(OnMapChangedListener<? extends ObservableMap<K, V>, K> listener);
+}
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/databinding/OnListChangedListener.java b/tools/data-binding/baseLibrary/src/main/java/android/databinding/OnListChangedListener.java
new file mode 100644
index 0000000..a76269e
--- /dev/null
+++ b/tools/data-binding/baseLibrary/src/main/java/android/databinding/OnListChangedListener.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+public interface OnListChangedListener {
+ void onChanged();
+ void onItemRangeChanged(int positionStart, int itemCount);
+ void onItemRangeInserted(int positionStart, int itemCount);
+ void onItemRangeMoved(int fromPosition, int toPosition, int itemCount);
+ void onItemRangeRemoved(int positionStart, int itemCount);
+}
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/databinding/OnMapChangedListener.java b/tools/data-binding/baseLibrary/src/main/java/android/databinding/OnMapChangedListener.java
new file mode 100644
index 0000000..647b1f7
--- /dev/null
+++ b/tools/data-binding/baseLibrary/src/main/java/android/databinding/OnMapChangedListener.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+public interface OnMapChangedListener<T extends ObservableMap<K, ?>, K> {
+ void onMapChanged(T sender, K key);
+}
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/databinding/OnPropertyChangedListener.java b/tools/data-binding/baseLibrary/src/main/java/android/databinding/OnPropertyChangedListener.java
new file mode 100644
index 0000000..4103f07
--- /dev/null
+++ b/tools/data-binding/baseLibrary/src/main/java/android/databinding/OnPropertyChangedListener.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding;
+
+public interface OnPropertyChangedListener {
+ public void onPropertyChanged(Observable sender, int fieldId);
+}
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/databinding/Untaggable.java b/tools/data-binding/baseLibrary/src/main/java/android/databinding/Untaggable.java
new file mode 100644
index 0000000..a1ce3ac
--- /dev/null
+++ b/tools/data-binding/baseLibrary/src/main/java/android/databinding/Untaggable.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface Untaggable {
+ String[] value();
+}
diff --git a/tools/data-binding/build.gradle b/tools/data-binding/build.gradle
new file mode 100644
index 0000000..675f196
--- /dev/null
+++ b/tools/data-binding/build.gradle
@@ -0,0 +1,74 @@
+Properties databindingProperties = new Properties()
+databindingProperties.load(new FileInputStream("${projectDir}/databinding.properties"))
+databindingProperties.mavenRepoDir = "${projectDir}/${databindingProperties.mavenRepoName}"
+ext.config = databindingProperties
+
+println "local maven repo is ${ext.config.mavenRepoDir}."
+
+new File(ext.config.mavenRepoDir).mkdir()
+subprojects {
+ apply plugin: 'maven'
+ group = config.group
+ version = config.snapshotVersion
+ repositories {
+ mavenCentral()
+ maven {
+ url "file://${config.mavenRepoDir}"
+ }
+ }
+ uploadArchives {
+ repositories {
+ mavenDeployer {
+ repository(url: "file://${config.mavenRepoDir}")
+ }
+ }
+ }
+}
+
+task deleteRepo(type: Delete) {
+ delete "${config.mavenRepoDir}"
+}
+
+def buildExtensionsTask = project.tasks.create "buildExtensionsTask", Exec
+buildExtensionsTask.workingDir file('extensions').getAbsolutePath()
+//on linux
+buildExtensionsTask.commandLine './gradlew'
+buildExtensionsTask.args 'clean', 'uploadArchives', '--info', '--stacktrace'
+buildExtensionsTask.dependsOn subprojects.uploadArchives
+
+file('integration-tests').listFiles().findAll { it.isDirectory() }.each {
+ println("Creating run test task for ${it.getAbsolutePath()}.")
+ def testTask = project.tasks.create "runTestsOf${it.getName().capitalize()}", Exec
+ testTask.workingDir it.getAbsolutePath()
+ //on linux
+ testTask.commandLine './gradlew'
+ testTask.args 'clean', 'connectedCheck', '--info', '--stacktrace'
+ testTask.dependsOn subprojects.uploadArchives
+ testTask.dependsOn buildExtensionsTask
+}
+
+task runIntegrationTests {
+ dependsOn tasks.findAll { task -> task.name.startsWith('runTestsOf') }
+}
+
+task runAllTests {
+ dependsOn runIntegrationTests
+}
+
+allprojects {
+ afterEvaluate { project ->
+ runAllTests.dependsOn project.tasks.findAll {task -> task.name.equals('test')}
+ runAllTests.dependsOn project.tasks.findAll {task -> task.name.equals('connectedCheck')}
+ }
+}
+
+subprojects.uploadArchives.each { it.shouldRunAfter deleteRepo }
+buildExtensionsTask.shouldRunAfter deleteRepo
+tasks['runTestsOfMultiModuleTestApp'].dependsOn tasks['runTestsOfIndependentLibrary']
+
+
+task rebuildRepo() {
+ dependsOn deleteRepo
+ dependsOn subprojects.uploadArchives
+ dependsOn buildExtensionsTask
+}
\ No newline at end of file
diff --git a/tools/data-binding/compiler/build.gradle b/tools/data-binding/compiler/build.gradle
new file mode 100644
index 0000000..af0aaf0
--- /dev/null
+++ b/tools/data-binding/compiler/build.gradle
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+apply plugin: 'java'
+apply plugin: "kotlin"
+
+
+sourceCompatibility = config.javaTargetCompatibility
+targetCompatibility = config.javaSourceCompatibility
+
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${config.kotlinVersion}"
+ }
+}
+
+
+dependencies {
+ compile 'junit:junit:4.12'
+ compile 'org.apache.commons:commons-lang3:3.3.2'
+ compile 'org.apache.commons:commons-io:1.3.2'
+ compile 'com.google.guava:guava:18.0'
+ compile "org.jetbrains.kotlin:kotlin-stdlib:${config.kotlinVersion}"
+ compile 'commons-codec:commons-codec:1.10'
+ compile project(":baseLibrary")
+ compile project(":grammarBuilder")
+ compile project(":xmlGrammar")
+ testCompile "com.android.databinding:libraryJar:$version@jar"
+}
+
+task fatJar(type: Jar) {
+ baseName = project.name + '-all'
+ from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
+ with jar
+}
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ pom.artifactId = 'compiler'
+ }
+ }
+}
+
+project(':library').afterEvaluate { libProject ->
+ tasks['compileTestKotlin'].dependsOn libProject.tasks['uploadJarArchives']
+}
diff --git a/tools/data-binding/compiler/gradle/wrapper/gradle-wrapper.jar b/tools/data-binding/compiler/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..3d0dee6
--- /dev/null
+++ b/tools/data-binding/compiler/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/tools/data-binding/compiler/gradle/wrapper/gradle-wrapper.properties b/tools/data-binding/compiler/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..29fb85e
--- /dev/null
+++ b/tools/data-binding/compiler/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Dec 11 16:05:38 PST 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-bin.zip
diff --git a/tools/data-binding/compiler/gradlew b/tools/data-binding/compiler/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/tools/data-binding/compiler/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/tools/data-binding/compiler/gradlew.bat b/tools/data-binding/compiler/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/tools/data-binding/compiler/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/Binding.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/Binding.java
new file mode 100644
index 0000000..f2bc96f
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/Binding.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool;
+
+import android.databinding.tool.expr.Expr;
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.store.SetterStore;
+import android.databinding.tool.store.SetterStore.SetterCall;
+
+public class Binding {
+
+ private final String mName;
+ private final Expr mExpr;
+ private final BindingTarget mTarget;
+ private SetterStore.SetterCall mSetterCall;
+
+ public Binding(BindingTarget target, String name, Expr expr) {
+ mTarget = target;
+ mName = name;
+ mExpr = expr;
+ }
+
+ private SetterStore.SetterCall getSetterCall() {
+ if (mSetterCall == null) {
+ ModelClass viewType = mTarget.getResolvedType();
+ if (viewType != null && viewType.extendsViewStub()) {
+ if (isViewStubAttribute()) {
+ mSetterCall = new ViewStubDirectCall(mName, viewType, mExpr);
+ } else {
+ mSetterCall = new ViewStubSetterCall(mName);
+ }
+ } else {
+ mSetterCall = SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(mName,
+ viewType, mExpr.getResolvedType(), mExpr.getModel().getImports());
+ }
+ }
+ return mSetterCall;
+ }
+
+ public BindingTarget getTarget() {
+ return mTarget;
+ }
+
+ public String toJavaCode(String targetViewName, String expressionCode) {
+ return getSetterCall().toJava(targetViewName, expressionCode);
+ }
+
+ /**
+ * The min api level in which this binding should be executed.
+ * <p>
+ * This should be the minimum value among the dependencies of this binding. For now, we only
+ * check the setter.
+ */
+ public int getMinApi() {
+ return getSetterCall().getMinApi();
+ }
+
+// private String resolveJavaCode(ModelAnalyzer modelAnalyzer) {
+//
+// }
+//// return modelAnalyzer.findMethod(mTarget.getResolvedType(), mName,
+//// Arrays.asList(mExpr.getResolvedType()));
+// //}
+//
+
+
+ public String getName() {
+ return mName;
+ }
+
+ public Expr getExpr() {
+ return mExpr;
+ }
+
+ private boolean isViewStubAttribute() {
+ if ("android:inflatedId".equals(mName)) {
+ return true;
+ } else if ("android:layout".equals(mName)) {
+ return true;
+ } else if ("android:visibility".equals(mName)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private static class ViewStubSetterCall extends SetterCall {
+ private final String mName;
+
+ public ViewStubSetterCall(String name) {
+ mName = name.substring(name.lastIndexOf(':') + 1);
+ }
+
+ @Override
+ protected String toJavaInternal(String viewExpression, String converted) {
+ return "if (" + viewExpression + ".isInflated()) " + viewExpression +
+ ".getBinding().setVariable(BR." + mName + ", " + converted + ")";
+ }
+
+ @Override
+ public int getMinApi() {
+ return 0;
+ }
+ }
+
+ private static class ViewStubDirectCall extends SetterCall {
+ private final SetterCall mWrappedCall;
+
+ public ViewStubDirectCall(String name, ModelClass viewType, Expr expr) {
+ mWrappedCall = SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(name,
+ viewType, expr.getResolvedType(), expr.getModel().getImports());
+ }
+
+ @Override
+ protected String toJavaInternal(String viewExpression, String converted) {
+ return "if (!" + viewExpression + ".isInflated()) " +
+ mWrappedCall.toJava(viewExpression + ".getViewStub()", converted);
+ }
+
+ @Override
+ public int getMinApi() {
+ return 0;
+ }
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/BindingTarget.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/BindingTarget.java
new file mode 100644
index 0000000..79156c0
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/BindingTarget.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool;
+
+import android.databinding.tool.expr.Expr;
+import android.databinding.tool.expr.ExprModel;
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.store.ResourceBundle;
+import android.databinding.tool.store.SetterStore;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BindingTarget {
+ List<Binding> mBindings = new ArrayList<Binding>();
+ ExprModel mModel;
+ ModelClass mResolvedClass;
+ String mFieldName;
+
+ // if this target presents itself in multiple layout files with different view types,
+ // it receives an interface type and should use it in the getter instead.
+ private ResourceBundle.BindingTargetBundle mBundle;
+
+ public BindingTarget(ResourceBundle.BindingTargetBundle bundle) {
+ mBundle = bundle;
+ }
+
+ public boolean isUsed() {
+ return mBundle.isUsed();
+ }
+
+ public void addBinding(String name, Expr expr) {
+ mBindings.add(new Binding(this, name, expr));
+ }
+
+ public String getInterfaceType() {
+ return mBundle.getInterfaceType() == null ? mBundle.getFullClassName() : mBundle.getInterfaceType();
+ }
+
+ public String getId() {
+ return mBundle.getId();
+ }
+
+ public String getTag() {
+ return mBundle.getTag();
+ }
+
+ public String getOriginalTag() {
+ return mBundle.getOriginalTag();
+ }
+
+ public String getViewClass() {
+ return mBundle.getFullClassName();
+ }
+
+ public ModelClass getResolvedType() {
+ if (mResolvedClass == null) {
+ mResolvedClass = ModelAnalyzer.getInstance().findClass(mBundle.getFullClassName(),
+ mModel.getImports());
+ }
+ return mResolvedClass;
+ }
+
+ public String getIncludedLayout() {
+ return mBundle.getIncludedLayout();
+ }
+
+ public boolean isBinder() {
+ return getIncludedLayout() != null;
+ }
+
+ public boolean supportsTag() {
+ return !SetterStore.get(ModelAnalyzer.getInstance())
+ .isUntaggable(mBundle.getFullClassName());
+ }
+
+ public List<Binding> getBindings() {
+ return mBindings;
+ }
+
+ public ExprModel getModel() {
+ return mModel;
+ }
+
+ public void setModel(ExprModel model) {
+ mModel = model;
+ }
+
+ public void setFieldName(String fieldName) {
+ mFieldName = fieldName;
+ }
+
+ public String getFieldName() {
+ return mFieldName;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/CompilerChef.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/CompilerChef.java
new file mode 100644
index 0000000..90829c5
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/CompilerChef.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool;
+
+import android.databinding.tool.store.ResourceBundle;
+import android.databinding.tool.util.L;
+import android.databinding.tool.writer.DataBinderWriter;
+import android.databinding.tool.writer.JavaFileWriter;
+
+/**
+ * Chef class for compiler.
+ *
+ * Different build systems can initiate a version of this to handle their work
+ */
+public class CompilerChef {
+ private JavaFileWriter mFileWriter;
+ private ResourceBundle mResourceBundle;
+ private DataBinder mDataBinder;
+
+ private CompilerChef() {
+ }
+
+ public static CompilerChef createChef(ResourceBundle bundle, JavaFileWriter fileWriter) {
+ CompilerChef chef = new CompilerChef();
+
+ chef.mResourceBundle = bundle;
+ chef.mFileWriter = fileWriter;
+ chef.mResourceBundle.validateMultiResLayouts();
+ return chef;
+ }
+
+ public ResourceBundle getResourceBundle() {
+ return mResourceBundle;
+ }
+
+ public void ensureDataBinder() {
+ if (mDataBinder == null) {
+ mDataBinder = new DataBinder(mResourceBundle);
+ mDataBinder.setFileWriter(mFileWriter);
+ }
+ }
+
+ public boolean hasAnythingToGenerate() {
+ L.d("checking if we have anything to generate. bundle size: %s",
+ mResourceBundle == null ? -1 : mResourceBundle.getLayoutBundles().size());
+ return mResourceBundle != null && mResourceBundle.getLayoutBundles().size() > 0;
+ }
+
+ public void writeDbrFile(int minSdk) {
+ ensureDataBinder();
+ final String pkg = "android.databinding";
+ DataBinderWriter dbr = new DataBinderWriter(pkg, mResourceBundle.getAppPackage(),
+ "DataBinderMapper", mDataBinder.getLayoutBinders(), minSdk);
+ if (dbr.getLayoutBinders().size() > 0) {
+ mFileWriter.writeToFile(pkg + "." + dbr.getClassName(), dbr.write());
+ }
+ }
+
+ /**
+ * Adds variables to list of Bindables.
+ */
+ public void addBRVariables(BindableHolder bindables) {
+ ensureDataBinder();
+ for (LayoutBinder layoutBinder : mDataBinder.mLayoutBinders) {
+ for (String variableName : layoutBinder.getUserDefinedVariables().keySet()) {
+ bindables.addVariable(variableName, layoutBinder.getClassName());
+ }
+ }
+ }
+
+ public void writeViewBinderInterfaces(boolean isLibrary) {
+ ensureDataBinder();
+ mDataBinder.writerBaseClasses(isLibrary);
+ }
+
+ public void writeViewBinders() {
+ ensureDataBinder();
+ mDataBinder.writeBinders();
+ }
+
+ public interface BindableHolder {
+ void addVariable(String variableName, String containingClassName);
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/DataBinder.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/DataBinder.java
new file mode 100644
index 0000000..70f8bcb
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/DataBinder.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool;
+
+import android.databinding.tool.store.ResourceBundle;
+import android.databinding.tool.util.L;
+import android.databinding.tool.writer.JavaFileWriter;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The main class that handles parsing files and generating classes.
+ */
+public class DataBinder {
+ List<LayoutBinder> mLayoutBinders = new ArrayList<LayoutBinder>();
+
+ private JavaFileWriter mFileWriter;
+
+ public DataBinder(ResourceBundle resourceBundle) {
+ L.d("reading resource bundle into data binder");
+ for (Map.Entry<String, List<ResourceBundle.LayoutFileBundle>> entry :
+ resourceBundle.getLayoutBundles().entrySet()) {
+ for (ResourceBundle.LayoutFileBundle bundle : entry.getValue()) {
+ mLayoutBinders.add(new LayoutBinder(resourceBundle, bundle));
+ }
+ }
+ }
+ public List<LayoutBinder> getLayoutBinders() {
+ return mLayoutBinders;
+ }
+
+ public void writerBaseClasses(boolean isLibrary) {
+ Set<String> writtenFiles = new HashSet<String>();
+ for (LayoutBinder layoutBinder : mLayoutBinders) {
+ if (isLibrary || layoutBinder.hasVariations()) {
+ String className = layoutBinder.getClassName();
+ if (writtenFiles.contains(className)) {
+ continue;
+ }
+ mFileWriter.writeToFile(layoutBinder.getPackage() + "." + className,
+ layoutBinder.writeViewBinderBaseClass());
+ writtenFiles.add(className);
+ }
+ }
+ }
+
+ public void writeBinders() {
+ for (LayoutBinder layoutBinder : mLayoutBinders) {
+ String className = layoutBinder.getImplementationName();
+ L.d("writing data binder %s", className);
+ mFileWriter.writeToFile(layoutBinder.getPackage() + "." + className,
+ layoutBinder.writeViewBinder());
+ }
+ }
+
+ public void setFileWriter(JavaFileWriter fileWriter) {
+ mFileWriter = fileWriter;
+ }
+
+ public JavaFileWriter getFileWriter() {
+ return mFileWriter;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/ExpressionParser.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/ExpressionParser.java
new file mode 100644
index 0000000..03687ff
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/ExpressionParser.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool;
+
+import org.antlr.v4.runtime.ANTLRInputStream;
+import org.antlr.v4.runtime.CommonTokenStream;
+
+import android.databinding.parser.BindingExpressionLexer;
+import android.databinding.parser.BindingExpressionParser;
+import android.databinding.tool.expr.Expr;
+import android.databinding.tool.expr.ExprModel;
+import android.databinding.tool.util.L;
+
+public class ExpressionParser {
+ final ExprModel mModel;
+ final ExpressionVisitor visitor;
+
+ public ExpressionParser(ExprModel model) {
+ mModel = model;
+ visitor = new ExpressionVisitor(mModel);
+ }
+
+ public Expr parse(String input) {
+ ANTLRInputStream inputStream = new ANTLRInputStream(input);
+ BindingExpressionLexer lexer = new BindingExpressionLexer(inputStream);
+ CommonTokenStream tokenStream = new CommonTokenStream(lexer);
+ BindingExpressionParser parser = new BindingExpressionParser(tokenStream);
+ BindingExpressionParser.BindingSyntaxContext root = parser.bindingSyntax();
+ L.d("exp tree: %s", root.toStringTree(parser));
+ return root.accept(visitor);
+ }
+
+ public ExprModel getModel() {
+ return mModel;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/ExpressionVisitor.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/ExpressionVisitor.java
new file mode 100644
index 0000000..a30f59b
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/ExpressionVisitor.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool;
+
+import com.google.common.base.Preconditions;
+
+import org.antlr.v4.runtime.misc.NotNull;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.TerminalNode;
+import org.apache.commons.lang3.ObjectUtils;
+
+import android.databinding.parser.BindingExpressionBaseVisitor;
+import android.databinding.parser.BindingExpressionParser;
+import android.databinding.tool.expr.Expr;
+import android.databinding.tool.expr.ExprModel;
+import android.databinding.tool.expr.StaticIdentifierExpr;
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ExpressionVisitor extends BindingExpressionBaseVisitor<Expr> {
+ private final ExprModel mModel;
+ public ExpressionVisitor(ExprModel model) {
+ mModel = model;
+ }
+
+ @Override
+ public Expr visitStringLiteral(@NotNull BindingExpressionParser.StringLiteralContext ctx) {
+ final String javaString;
+ if (ctx.SingleQuoteString() != null) {
+ String str = ctx.SingleQuoteString().getText();
+ String contents = str.substring(1, str.length() - 1);
+ contents = contents.replace("\"", "\\\"").replace("\\`", "`");
+ javaString = '"' + contents + '"';
+ } else {
+ javaString = ctx.DoubleQuoteString().getText();
+ }
+
+ return mModel.symbol(javaString, String.class);
+ }
+
+ @Override
+ public Expr visitGrouping(@NotNull BindingExpressionParser.GroupingContext ctx) {
+ Preconditions.checkArgument(ctx.children.size() == 3, "Grouping expression should have"
+ + " 3 children. # of children: %d", ctx.children.size());
+ return mModel.group(ctx.children.get(1).accept(this));
+ }
+
+ @Override
+ public Expr visitBindingSyntax(@NotNull BindingExpressionParser.BindingSyntaxContext ctx) {
+ try {
+ // TODO handle defaults
+ return mModel.bindingExpr(ctx.expression().accept(this));
+ } catch (Exception e) {
+ System.out.println("Error while parsing! " + ctx.getText());
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Expr visitDotOp(@NotNull BindingExpressionParser.DotOpContext ctx) {
+ ModelAnalyzer analyzer = ModelAnalyzer.getInstance();
+ ModelClass modelClass = analyzer.findClass(ctx.getText(), mModel.getImports());
+ if (modelClass == null) {
+ return mModel.field(ctx.expression().accept(this),
+ ctx.Identifier().getSymbol().getText());
+ } else {
+ String name = modelClass.toJavaCode();
+ StaticIdentifierExpr expr = mModel.staticIdentifier(name);
+ expr.setUserDefinedType(name);
+ return expr;
+ }
+ }
+
+ @Override
+ public Expr visitQuestionQuestionOp(@NotNull BindingExpressionParser.QuestionQuestionOpContext ctx) {
+ final Expr left = ctx.left.accept(this);
+ return mModel.ternary(mModel.comparison("==", left, mModel.symbol("null", Object.class)),
+ ctx.right.accept(this), left);
+ }
+
+ @Override
+ public Expr visitTerminal(@NotNull TerminalNode node) {
+ final int type = node.getSymbol().getType();
+ Class classType;
+ switch (type) {
+ case BindingExpressionParser.IntegerLiteral:
+ classType = int.class;
+ break;
+ case BindingExpressionParser.FloatingPointLiteral:
+ classType = float.class;
+ break;
+ case BindingExpressionParser.BooleanLiteral:
+ classType = boolean.class;
+ break;
+ case BindingExpressionParser.CharacterLiteral:
+ classType = char.class;
+ break;
+ case BindingExpressionParser.SingleQuoteString:
+ case BindingExpressionParser.DoubleQuoteString:
+ classType = String.class;
+ break;
+ case BindingExpressionParser.NullLiteral:
+ classType = Object.class;
+ break;
+ default:
+ throw new RuntimeException("cannot create expression from terminal node " + node.toString());
+ }
+ return mModel.symbol(node.getText(), classType);
+ }
+
+ @Override
+ public Expr visitComparisonOp(@NotNull BindingExpressionParser.ComparisonOpContext ctx) {
+ return mModel.comparison(ctx.op.getText(), ctx.left.accept(this), ctx.right.accept(this));
+ }
+
+ @Override
+ public Expr visitIdentifier(@NotNull BindingExpressionParser.IdentifierContext ctx) {
+ return mModel.identifier(ctx.getText());
+ }
+
+ @Override
+ public Expr visitTernaryOp(@NotNull BindingExpressionParser.TernaryOpContext ctx) {
+ return mModel.ternary(ctx.left.accept(this), ctx.iftrue.accept(this),
+ ctx.iffalse.accept(this));
+ }
+
+ @Override
+ public Expr visitMethodInvocation(
+ @NotNull BindingExpressionParser.MethodInvocationContext ctx) {
+ List<Expr> args = new ArrayList<Expr>();
+ if (ctx.args != null) {
+ for (ParseTree item : ctx.args.children) {
+ if (ObjectUtils.equals(item.getText(), ",")) {
+ continue;
+ }
+ args.add(item.accept(this));
+ }
+ }
+ return mModel.methodCall(ctx.target.accept(this),
+ ctx.Identifier().getText(), args);
+ }
+
+ @Override
+ public Expr visitMathOp(@NotNull BindingExpressionParser.MathOpContext ctx) {
+ return mModel.math(ctx.left.accept(this), ctx.op.getText(), ctx.right.accept(this));
+ }
+
+ @Override
+ public Expr visitResources(@NotNull BindingExpressionParser.ResourcesContext ctx) {
+ final List<Expr> args = new ArrayList<Expr>();
+ if (ctx.resourceParameters() != null) {
+ for (ParseTree item : ctx.resourceParameters().expressionList().children) {
+ if (ObjectUtils.equals(item.getText(), ",")) {
+ continue;
+ }
+ args.add(item.accept(this));
+ }
+ }
+ final String resourceReference = ctx.ResourceReference().getText();
+ final int colonIndex = resourceReference.indexOf(':');
+ final int slashIndex = resourceReference.indexOf('/');
+ final String packageName = colonIndex < 0 ? null :
+ resourceReference.substring(1, colonIndex).trim();
+ final int startIndex = Math.max(1, colonIndex + 1);
+ final String resourceType = resourceReference.substring(startIndex, slashIndex).trim();
+ final String resourceName = resourceReference.substring(slashIndex + 1).trim();
+ return mModel.resourceExpr(packageName, resourceType, resourceName, args);
+ }
+
+ @Override
+ public Expr visitBracketOp(@NotNull BindingExpressionParser.BracketOpContext ctx) {
+ return mModel.bracketExpr(visit(ctx.expression(0)), visit(ctx.expression(1)));
+ }
+
+ @Override
+ public Expr visitCastOp(@NotNull BindingExpressionParser.CastOpContext ctx) {
+ return mModel.castExpr(ctx.type().getText(), visit(ctx.expression()));
+ }
+
+ // @Override
+// public Expr visitIdentifier(@NotNull BindingExpressionParser.IdentifierContext ctx) {
+// final String identifier = ctx.Identifier().getText();
+// final VariableRef variableRef = mModel.getOrCreateVariable(identifier, null);
+// mAccessedVariables.add(variableRef);
+//
+// return new FieldExpr(variableRef, new ArrayList<VariableRef>(0));
+// }
+//
+// @Override
+// public Expr visit(@NotNull ParseTree tree) {
+// if (tree == null) {
+// return null;
+// }
+// return super.visit(tree);
+// }
+//
+// @Override
+// public Expr visitTernaryOp(@NotNull BindingExpressionParser.TernaryOpContext ctx) {
+// return new TernaryExpr(ctx.left.accept(this), ctx.iftrue.accept(this), ctx.iffalse.accept(this));
+// }
+//
+// @Override
+// public Expr visitTerminal(@NotNull TerminalNode node) {
+//
+// final int type = node.getSymbol().getType();
+// switch (type) {
+// case IntegerLiteral:
+// return new SymbolExpr(node.getText(), Integer.class);
+// case FloatingPointLiteral:
+// return new SymbolExpr(node.getText(), Float.class);
+// case BooleanLiteral:
+// return new SymbolExpr(node.getText(), Boolean.class);
+// case CharacterLiteral:
+// return new SymbolExpr(node.getText(), Character.class);
+// case SingleQuoteString:
+// return new SymbolExpr(node.getText(), String.class);
+// case DoubleQuoteString:
+// return new SymbolExpr(node.getText(), String.class);
+// case NullLiteral:
+// return new SymbolExpr(node.getText(), Object.class);
+// default:
+// throw new RuntimeException("cannot create expression from terminal node " + node.toString());
+// }
+// }
+//
+// @Override
+// public Expr visitMathOp(@NotNull BindingExpressionParser.MathOpContext ctx) {
+// // TODO must support upper cast
+// return new OpExpr(ctx.left.accept(this), ctx.op.getText(), ctx.right.accept(this));
+// }
+//
+// @Override
+// public Expr visitBitShiftOp(@NotNull BindingExpressionParser.BitShiftOpContext ctx) {
+// return new BinaryOpExpr(ctx.left.accept(this), ctx.op.getText(), ctx.right.accept(this));
+// }
+//
+// @Override
+// public Expr visitComparisonOp(@NotNull BindingExpressionParser.ComparisonOpContext ctx) {
+// return new ComparisonOpExpr(ctx.left.accept(this), ctx.op.getText(), ctx.right.accept(this));
+// }
+//
+// @Override
+// public Expr visitBinaryOp(@NotNull BindingExpressionParser.BinaryOpContext ctx) {
+// return new BinaryOpExpr(ctx.left.accept(this), ctx.op.getText(), ctx.right.accept(this));
+// }
+//
+// @Override
+// public Expr visitAndOrOp(@NotNull BindingExpressionParser.AndOrOpContext ctx) {
+// return new AndOrOpExpr(ctx.left.accept(this), ctx.op.getText(), ctx.right.accept(this));
+// }
+//
+// @Override
+// protected Expr aggregateResult(final Expr aggregate, final Expr nextResult) {
+// if (aggregate == null) {
+// return nextResult;
+// } else {
+// return new Expr() {
+// @org.jetbrains.annotations.NotNull
+// @Override
+// public Class<? extends Object> resolveValueType(
+// @org.jetbrains.annotations.NotNull ModelAnalyzer modelAnalyzer) {
+// return modelAnalyzer.commonParentOf(aggregate.getResolvedClass(), nextResult.getResolvedClass());
+// }
+//
+// @org.jetbrains.annotations.NotNull
+// @Override
+// public String toReadableString() {
+// return aggregate.toReadableString() + ' ' + nextResult.toReadableString();
+// }
+//
+// @org.jetbrains.annotations.NotNull
+// @Override
+// public String toJava() {
+// return aggregate.toJava() + ' ' + nextResult.toJava();
+// }
+// };
+// }
+// }
+//
+// @Override
+// public Expr visitDefaults(@NotNull BindingExpressionParser.DefaultsContext ctx) {
+// return visit(ctx.constantValue());
+// }
+//
+// @Override
+// public Expr visitMethodInvocation(
+// @NotNull BindingExpressionParser.MethodInvocationContext ctx) {
+// final Expr expression = visit(ctx.expression());
+// final String methodName = ctx.Identifier().getText();
+// final ArrayList<Expr> parameters = new ArrayList<>();
+// if (ctx.expressionList() != null) {
+// for (BindingExpressionParser.ExpressionContext parameter : ctx.expressionList()
+// .expression()) {
+// parameters.add(visit(parameter));
+// }
+// }
+// return new MethodCallExpr(expression, methodName, parameters);
+// }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/LayoutBinder.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/LayoutBinder.java
new file mode 100644
index 0000000..e0b18d9
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/LayoutBinder.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool;
+
+import com.google.common.base.Preconditions;
+
+import android.databinding.tool.expr.Dependency;
+import android.databinding.tool.expr.Expr;
+import android.databinding.tool.expr.ExprModel;
+import android.databinding.tool.expr.IdentifierExpr;
+import android.databinding.tool.store.ResourceBundle;
+import android.databinding.tool.store.ResourceBundle.BindingTargetBundle;
+import android.databinding.tool.util.ParserHelper;
+import android.databinding.tool.writer.LayoutBinderWriter;
+import android.databinding.tool.writer.WriterPackage;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Keeps all information about the bindings per layout file
+ */
+public class LayoutBinder {
+ private static final Comparator<BindingTarget> COMPARE_FIELD_NAME = new Comparator<BindingTarget>() {
+ @Override
+ public int compare(BindingTarget first, BindingTarget second) {
+ final String fieldName1 = WriterPackage.getFieldName(first);
+ final String fieldName2 = WriterPackage.getFieldName(second);
+ return fieldName1.compareTo(fieldName2);
+ }
+ };
+
+ /*
+ * val pkg: String, val projectPackage: String, val baseClassName: String,
+ val layoutName:String, val lb: LayoutExprBinding*/
+ private final ExprModel mExprModel;
+ private final ExpressionParser mExpressionParser;
+ private final List<BindingTarget> mBindingTargets;
+ private String mPackage;
+ private String mModulePackage;
+ private String mProjectPackage;
+ private String mBaseClassName;
+ private final HashMap<String, String> mUserDefinedVariables = new HashMap<String, String>();
+
+ private LayoutBinderWriter mWriter;
+ private ResourceBundle.LayoutFileBundle mBundle;
+
+ public LayoutBinder(ResourceBundle resourceBundle,
+ ResourceBundle.LayoutFileBundle layoutBundle) {
+ mExprModel = new ExprModel();
+ mExpressionParser = new ExpressionParser(mExprModel);
+ mBindingTargets = new ArrayList<BindingTarget>();
+ mBundle = layoutBundle;
+ mProjectPackage = resourceBundle.getAppPackage();
+ mModulePackage = layoutBundle.getModulePackage();
+ mPackage = layoutBundle.getModulePackage() + ".databinding";
+ mBaseClassName = ParserHelper.INSTANCE$.toClassName(layoutBundle.getFileName()) + "Binding";
+ // copy over data.
+ for (Map.Entry<String, String> variable : mBundle.getVariables().entrySet()) {
+ addVariable(variable.getKey(), variable.getValue());
+ }
+
+ for (Map.Entry<String, String> userImport : mBundle.getImports().entrySet()) {
+ mExprModel.addImport(userImport.getKey(), userImport.getValue());
+ }
+ for (BindingTargetBundle targetBundle : mBundle.getBindingTargetBundles()) {
+ final BindingTarget bindingTarget = createBindingTarget(targetBundle);
+ for (ResourceBundle.BindingTargetBundle.BindingBundle bindingBundle : targetBundle
+ .getBindingBundleList()) {
+ bindingTarget.addBinding(bindingBundle.getName(), parse(bindingBundle.getExpr()));
+ }
+ }
+ Collections.sort(mBindingTargets, COMPARE_FIELD_NAME);
+ }
+
+ public void resolveWhichExpressionsAreUsed() {
+ List<Expr> used = new ArrayList<Expr>();
+ for (BindingTarget target : mBindingTargets) {
+ for (Binding binding : target.getBindings()) {
+ binding.getExpr().setIsUsed(true);
+ used.add(binding.getExpr());
+ }
+ }
+ while (!used.isEmpty()) {
+ Expr e = used.remove(used.size() - 1);
+ for (Dependency dep : e.getDependencies()) {
+ if (!dep.getOther().isUsed()) {
+ used.add(dep.getOther());
+ dep.getOther().setIsUsed(true);
+ }
+ }
+ }
+ }
+
+ public IdentifierExpr addVariable(String name, String type) {
+ Preconditions.checkState(!mUserDefinedVariables.containsKey(name),
+ "%s has already been defined as %s", name, type);
+ final IdentifierExpr id = mExprModel.identifier(name);
+ id.setUserDefinedType(type);
+ id.enableDirectInvalidation();
+ mUserDefinedVariables.put(name, type);
+ return id;
+ }
+
+ public HashMap<String, String> getUserDefinedVariables() {
+ return mUserDefinedVariables;
+ }
+
+ public BindingTarget createBindingTarget(ResourceBundle.BindingTargetBundle targetBundle) {
+ final BindingTarget target = new BindingTarget(targetBundle);
+ mBindingTargets.add(target);
+ target.setModel(mExprModel);
+ return target;
+ }
+
+ public Expr parse(String input) {
+ final Expr parsed = mExpressionParser.parse(input);
+ parsed.setBindingExpression(true);
+ return parsed;
+ }
+
+ public List<BindingTarget> getBindingTargets() {
+ return mBindingTargets;
+ }
+
+ public boolean isEmpty() {
+ return mExprModel.size() == 0;
+ }
+
+ public ExprModel getModel() {
+ return mExprModel;
+ }
+
+ private void ensureWriter() {
+ if (mWriter == null) {
+ mWriter = new LayoutBinderWriter(this);
+ }
+ }
+
+ public String writeViewBinderBaseClass() {
+ ensureWriter();
+ return mWriter.writeBaseClass();
+ }
+
+
+ public String writeViewBinder() {
+ mExprModel.seal();
+ ensureWriter();
+ Preconditions.checkNotNull(mPackage, "package cannot be null");
+ Preconditions.checkNotNull(mProjectPackage, "project package cannot be null");
+ Preconditions.checkNotNull(mBaseClassName, "base class name cannot be null");
+ return mWriter.write();
+ }
+
+ public String getPackage() {
+ return mPackage;
+ }
+
+ public String getModulePackage() {
+ return mModulePackage;
+ }
+
+ public void setPackage(String aPackage) {
+ mPackage = aPackage;
+ }
+
+ public String getProjectPackage() {
+ return mProjectPackage;
+ }
+
+ public String getLayoutname() {
+ return mBundle.getFileName();
+ }
+
+ public String getImplementationName() {
+ if (hasVariations()) {
+ return mBaseClassName + mBundle.getConfigName() + "Impl";
+ } else {
+ return mBaseClassName;
+ }
+ }
+
+ public String getClassName() {
+ return mBaseClassName;
+ }
+
+ public String getTag() {
+ return mBundle.getDirectory() + "/" + mBundle.getFileName();
+ }
+
+ public boolean hasVariations() {
+ return mBundle.hasVariations();
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/LayoutXmlProcessor.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/LayoutXmlProcessor.java
new file mode 100644
index 0000000..8cc01d2
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/LayoutXmlProcessor.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.xml.sax.SAXException;
+
+import android.databinding.BindingBuildInfo;
+import android.databinding.tool.store.LayoutFileParser;
+import android.databinding.tool.store.ResourceBundle;
+import android.databinding.tool.writer.JavaFileWriter;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPathExpressionException;
+
+/**
+ * Processes the layout XML, stripping the binding attributes and elements
+ * and writes the information into an annotated class file for the annotation
+ * processor to work with.
+ */
+public class LayoutXmlProcessor {
+ // hardcoded in baseAdapters
+ public static final String RESOURCE_BUNDLE_PACKAGE = "android.databinding.layouts";
+ public static final String CLASS_NAME = "DataBindingInfo";
+ private final JavaFileWriter mFileWriter;
+ private final ResourceBundle mResourceBundle;
+ private final int mMinSdk;
+
+ private boolean mProcessingComplete;
+ private boolean mWritten;
+ private final boolean mIsLibrary;
+ private final String mBuildId = UUID.randomUUID().toString();
+ // can be a list of xml files or folders that contain XML files
+ private final List<File> mResources;
+
+ public LayoutXmlProcessor(String applicationPackage, List<File> resources,
+ JavaFileWriter fileWriter, int minSdk, boolean isLibrary) {
+ mFileWriter = fileWriter;
+ mResourceBundle = new ResourceBundle(applicationPackage);
+ mResources = resources;
+ mMinSdk = minSdk;
+ mIsLibrary = isLibrary;
+ }
+
+ public static List<File> getLayoutFiles(List<File> resources) {
+ List<File> result = new ArrayList<File>();
+ for (File resource : Iterables.filter(resources, fileExists)) {
+ if (resource.isDirectory()) {
+ for (File layoutFolder : resource.listFiles(layoutFolderFilter)) {
+ for (File xmlFile : layoutFolder.listFiles(xmlFileFilter)) {
+ result.add(xmlFile);
+ }
+
+ }
+ } else if (xmlFileFilter.accept(resource.getParentFile(), resource.getName())) {
+ result.add(resource);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * used by the studio plugin
+ */
+ public ResourceBundle getResourceBundle() {
+ return mResourceBundle;
+ }
+
+ public boolean processResources()
+ throws ParserConfigurationException, SAXException, XPathExpressionException,
+ IOException {
+ if (mProcessingComplete) {
+ return false;
+ }
+ LayoutFileParser layoutFileParser = new LayoutFileParser();
+ for (File xmlFile : getLayoutFiles(mResources)) {
+ final ResourceBundle.LayoutFileBundle bindingLayout = layoutFileParser
+ .parseXml(xmlFile, mResourceBundle.getAppPackage());
+ if (bindingLayout != null && !bindingLayout.isEmpty()) {
+ mResourceBundle.addLayoutBundle(bindingLayout);
+ }
+ }
+ mProcessingComplete = true;
+ return true;
+ }
+
+ public void writeIntermediateFile(File sdkDir, File xmlOutDir) throws JAXBException {
+ if (mWritten) {
+ return;
+ }
+ JAXBContext context = JAXBContext.newInstance(ResourceBundle.LayoutFileBundle.class);
+ Marshaller marshaller = context.createMarshaller();
+ writeInfoClass(sdkDir, xmlOutDir);
+ for (List<ResourceBundle.LayoutFileBundle> layouts : mResourceBundle.getLayoutBundles()
+ .values()) {
+ for (ResourceBundle.LayoutFileBundle layout : layouts) {
+ writeXmlFile(xmlOutDir, layout, marshaller);
+ }
+ }
+ mWritten = true;
+ }
+
+ private void writeXmlFile(File xmlOutDir, ResourceBundle.LayoutFileBundle layout,
+ Marshaller marshaller) throws JAXBException {
+ String filename = generateExportFileName(layout) + ".xml";
+ String xml = toXML(layout, marshaller);
+ mFileWriter.writeToFile(new File(xmlOutDir, filename), xml);
+ }
+
+ public String getInfoClassFullName() {
+ return RESOURCE_BUNDLE_PACKAGE + "." + CLASS_NAME;
+ }
+
+ private String toXML(ResourceBundle.LayoutFileBundle layout, Marshaller marshaller)
+ throws JAXBException {
+ StringWriter writer = new StringWriter();
+ marshaller.marshal(layout, writer);
+ return writer.getBuffer().toString();
+ }
+
+ /**
+ * Generates a string identifier that can uniquely identify the given layout bundle.
+ * This identifier can be used when we need to export data about this layout bundle.
+ */
+ private String generateExportFileName(ResourceBundle.LayoutFileBundle layout) {
+ StringBuilder name = new StringBuilder(layout.getFileName());
+ name.append('-').append(layout.getDirectory());
+ for (int i = name.length() - 1; i >= 0; i--) {
+ char c = name.charAt(i);
+ if (c == '-') {
+ name.deleteCharAt(i);
+ c = Character.toUpperCase(name.charAt(i));
+ name.setCharAt(i, c);
+ }
+ }
+ return name.toString();
+ }
+
+ private void writeInfoClass(File sdkDir, File xmlOutDir) {
+ final String sdkPath = StringEscapeUtils.escapeJava(sdkDir.getAbsolutePath());
+ final Class annotation = BindingBuildInfo.class;
+ final String layoutInfoPath = StringEscapeUtils.escapeJava(xmlOutDir.getAbsolutePath());
+ String classString = "package " + RESOURCE_BUNDLE_PACKAGE + ";\n\n" +
+ "import " + annotation.getCanonicalName() + ";\n\n" +
+ "@" + annotation.getSimpleName() + "(buildId=\"" + mBuildId + "\", " +
+ "modulePackage=\"" + mResourceBundle.getAppPackage() + "\", " +
+ "sdkRoot=\"" + sdkPath + "\", " +
+ "layoutInfoDir=\"" + layoutInfoPath + "\"," +
+ "isLibrary=" + mIsLibrary + "," +
+ "minSdk=" + mMinSdk + ")\n" +
+ "public class " + CLASS_NAME + " {}\n";
+ mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + "." + CLASS_NAME, classString);
+ }
+
+ private static final Predicate<File> fileExists = new Predicate<File>() {
+ @Override
+ public boolean apply(File input) {
+ return input.exists() && input.canRead();
+ }
+ };
+
+ private static final FilenameFilter layoutFolderFilter = new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.startsWith("layout");
+ }
+ };
+
+ private static final FilenameFilter xmlFileFilter = new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.toLowerCase().endsWith(".xml");
+ }
+ };
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/MakeCopy.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/MakeCopy.java
new file mode 100644
index 0000000..ac585d1
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/MakeCopy.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.tool;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.w3c.dom.Document;
+
+import android.databinding.tool.writer.JavaFileWriter;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+/**
+ * This class is used by make to copy resources to an intermediate directory and start processing
+ * them. When aapt takes over, this can be easily extracted to a short script.
+ */
+public class MakeCopy {
+ private static final int MANIFEST_INDEX = 0;
+ private static final int ADK_INDEX = 1;
+ private static final int SRC_INDEX = 2;
+ private static final int XML_INDEX = 3;
+ private static final int RES_OUT_INDEX = 4;
+ private static final int RES_IN_INDEX = 5;
+
+ private static final String APP_SUBPATH = LayoutXmlProcessor.RESOURCE_BUNDLE_PACKAGE
+ .replace('.', File.separatorChar);
+ private static final FilenameFilter LAYOUT_DIR_FILTER = new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.toLowerCase().startsWith("layout");
+ }
+ };
+
+ private static final FilenameFilter XML_FILENAME_FILTER = new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.toLowerCase().endsWith(".xml");
+ }
+ };
+
+ public static void main(String[] args) {
+ if (args.length < 6) {
+ System.out.println("required parameters: manifest adk-dir src-out-dir xml-out-dir " +
+ "res-out-dir res-in-dir...");
+ System.out.println("Creates an android data binding class and copies resources from");
+ System.out.println("res-source to res-target and modifies binding layout files");
+ System.out.println("in res-target. Binding data is extracted into XML files");
+ System.out.println("and placed in xml-out-dir.");
+ System.out.println(" manifest path to AndroidManifest.xml file");
+ System.out.println(" adk-dir path to Android SDK home");
+ System.out.println(" src-out-dir path to where generated source goes");
+ System.out.println(" xml-out-dir path to where generated binding XML goes");
+ System.out.println(" res-out-dir path to the where modified resources should go");
+ System.out.println(" res-in-dir path to source resources \"res\" directory. One" +
+ " or more are allowed.");
+ System.exit(1);
+ }
+ final boolean isLibrary;
+ final String applicationPackage;
+ final int minSdk;
+ final Document androidManifest = readAndroidManifest(new File(args[MANIFEST_INDEX]));
+ try {
+ final XPathFactory xPathFactory = XPathFactory.newInstance();
+ final XPath xPath = xPathFactory.newXPath();
+ isLibrary = (Boolean) xPath.evaluate("boolean(/manifest/application)", androidManifest,
+ XPathConstants.BOOLEAN);
+ applicationPackage = xPath.evaluate("string(/manifest/@package)", androidManifest);
+ final Double minSdkNumber = (Double) xPath.evaluate(
+ "number(/manifest/uses-sdk/@android:minSdkVersion)", androidManifest,
+ XPathConstants.NUMBER);
+ minSdk = minSdkNumber == null ? 1 : minSdkNumber.intValue();
+ } catch (XPathExpressionException e) {
+ e.printStackTrace();
+ System.exit(6);
+ return;
+ }
+ final File srcDir = new File(args[SRC_INDEX], APP_SUBPATH);
+ if (!makeTargetDir(srcDir)) {
+ System.err.println("Could not create source directory " + srcDir);
+ System.exit(2);
+ }
+ final File resTarget = new File(args[RES_OUT_INDEX]);
+ if (!makeTargetDir(resTarget)) {
+ System.err.println("Could not create resource directory: " + resTarget);
+ System.exit(4);
+ }
+ final File xmlDir = new File(args[XML_INDEX]);
+ if (!makeTargetDir(xmlDir)) {
+ System.err.println("Could not create xml output directory: " + xmlDir);
+ System.exit(5);
+ }
+ final File adkDir = new File(args[ADK_INDEX]);
+ if (!adkDir.exists()) {
+ System.err.println("Could not find android SDK directory: " + adkDir);
+ System.exit(6);
+ }
+ System.out.println("Application Package: " + applicationPackage);
+ System.out.println("Minimum SDK: " + minSdk);
+ System.out.println("Target Resources: " + resTarget.getAbsolutePath());
+ System.out.println("Target Source Dir: " + srcDir.getAbsolutePath());
+ System.out.println("Target XML Dir: " + xmlDir.getAbsolutePath());
+
+ boolean foundSomeResources = false;
+ for (int i = RES_IN_INDEX; i < args.length; i++) {
+ final File resDir = new File(args[i]);
+ if (!resDir.exists()) {
+ System.err.println("Could not find resource directory: " + resDir);
+ } else {
+ System.out.println("Source Resources: " + resDir.getAbsolutePath());
+ try {
+ FileUtils.copyDirectory(resDir, resTarget);
+ addFromFile(resDir, resTarget);
+ foundSomeResources = true;
+ } catch (IOException e) {
+ System.err.println("Could not copy resources from " + resDir + " to " + resTarget +
+ ": " + e.getLocalizedMessage());
+ System.exit(3);
+ }
+ }
+ }
+
+ if (!foundSomeResources) {
+ System.err.println("No resource directories were found.");
+ System.exit(7);
+ }
+ processLayoutFiles(applicationPackage, resTarget, srcDir, xmlDir, adkDir, minSdk,
+ isLibrary);
+ }
+
+ private static Document readAndroidManifest(File manifest) {
+ try {
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
+ return documentBuilder.parse(manifest);
+ } catch (Exception e) {
+ System.err.println("Could not load Android Manifest from " +
+ manifest.getAbsolutePath() + ": " + e.getLocalizedMessage());
+ System.exit(8);
+ return null;
+ }
+ }
+
+ private static void processLayoutFiles(String applicationPackage, File resTarget, File srcDir,
+ File xmlDir, File adkDir, int minSdk, boolean isLibrary) {
+ ArrayList<File> resourceFolders = new ArrayList<File>();
+ resourceFolders.add(resTarget);
+ MakeFileWriter makeFileWriter = new MakeFileWriter(srcDir);
+ LayoutXmlProcessor xmlProcessor = new LayoutXmlProcessor(applicationPackage,
+ resourceFolders, makeFileWriter, minSdk, isLibrary);
+ try {
+ xmlProcessor.processResources();
+ xmlProcessor.writeIntermediateFile(adkDir, xmlDir);
+ if (makeFileWriter.getErrorCount() > 0) {
+ System.exit(9);
+ }
+ } catch (Exception e) {
+ System.err.println("Error processing layout files: " + e.getLocalizedMessage());
+ System.exit(10);
+ }
+ }
+
+ private static void addFromFile(File resDir, File resTarget) {
+ for (File layoutDir : resDir.listFiles(LAYOUT_DIR_FILTER)) {
+ if (layoutDir.isDirectory()) {
+ File targetDir = new File(resTarget, layoutDir.getName());
+ for (File layoutFile : layoutDir.listFiles(XML_FILENAME_FILTER)) {
+ File targetFile = new File(targetDir, layoutFile.getName());
+ FileWriter appender = null;
+ try {
+ appender = new FileWriter(targetFile, true);
+ appender.write("<!-- From: " + layoutFile.toURI().toString() + " -->\n");
+ } catch (IOException e) {
+ System.err.println("Could not update " + layoutFile + ": " +
+ e.getLocalizedMessage());
+ } finally {
+ IOUtils.closeQuietly(appender);
+ }
+ }
+ }
+ }
+ }
+
+ private static boolean makeTargetDir(File dir) {
+ if (dir.exists()) {
+ return dir.isDirectory();
+ }
+
+ return dir.mkdirs();
+ }
+
+ private static class MakeFileWriter extends JavaFileWriter {
+ private final File mSourceRoot;
+ private int mErrorCount;
+
+ public MakeFileWriter(File sourceRoot) {
+ mSourceRoot = sourceRoot;
+ }
+
+ @Override
+ public void writeToFile(String canonicalName, String contents) {
+ String fileName = canonicalName.replace('.', File.separatorChar) + ".java";
+ File sourceFile = new File(mSourceRoot, fileName);
+ FileWriter writer = null;
+ try {
+ sourceFile.getParentFile().mkdirs();
+ writer = new FileWriter(sourceFile);
+ writer.write(contents);
+ } catch (IOException e) {
+ System.err.println("Could not write to " + sourceFile + ": " +
+ e.getLocalizedMessage());
+ mErrorCount++;
+ } finally {
+ IOUtils.closeQuietly(writer);
+ }
+ }
+
+ public int getErrorCount() {
+ return mErrorCount;
+ }
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/BracketExpr.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/BracketExpr.java
new file mode 100644
index 0000000..b5cce7d
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/BracketExpr.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+
+import java.util.List;
+
+public class BracketExpr extends Expr {
+
+ public static enum BracketAccessor {
+ ARRAY,
+ LIST,
+ MAP,
+ }
+
+ private BracketAccessor mAccessor;
+
+ BracketExpr(Expr target, Expr arg) {
+ super(target, arg);
+ }
+
+ @Override
+ protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+ ModelClass targetType = getTarget().resolveType(modelAnalyzer);
+ if (targetType.isArray()) {
+ mAccessor = BracketAccessor.ARRAY;
+ } else if (targetType.isList()) {
+ mAccessor = BracketAccessor.LIST;
+ } else if (targetType.isMap()) {
+ mAccessor = BracketAccessor.MAP;
+ } else {
+ throw new IllegalArgumentException("Cannot determine variable type used in [] " +
+ "expression. Cast the value to List, Map, " +
+ "or array. Type detected: " + targetType.toJavaCode());
+ }
+ return targetType.getComponentType();
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ return constructDynamicChildrenDependencies();
+ }
+
+ protected String computeUniqueKey() {
+ return sUniqueKeyJoiner.join(getTarget().computeUniqueKey(), "$", getArg().computeUniqueKey(), "$");
+ }
+
+ public Expr getTarget() {
+ return getChildren().get(0);
+ }
+
+ public Expr getArg() {
+ return getChildren().get(1);
+ }
+
+ public BracketAccessor getAccessor() {
+ return mAccessor;
+ }
+
+ public boolean argCastsInteger() {
+ return Object.class.equals(getArg().getResolvedType());
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/CastExpr.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/CastExpr.java
new file mode 100644
index 0000000..b4e41da
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/CastExpr.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+
+import java.util.List;
+
+public class CastExpr extends Expr {
+
+ final String mType;
+
+ CastExpr(String type, Expr expr) {
+ super(expr);
+ mType = type;
+ }
+
+ @Override
+ protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+ return modelAnalyzer.findClass(mType, getModel().getImports());
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ final List<Dependency> dependencies = constructDynamicChildrenDependencies();
+ for (Dependency dependency : dependencies) {
+ dependency.setMandatory(true);
+ }
+ return dependencies;
+ }
+
+ protected String computeUniqueKey() {
+ return sUniqueKeyJoiner.join(mType, getCastExpr().computeUniqueKey());
+ }
+
+ public Expr getCastExpr() {
+ return getChildren().get(0);
+ }
+
+ public String getCastType() {
+ return getResolvedType().toJavaCode();
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/ComparisonExpr.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/ComparisonExpr.java
new file mode 100644
index 0000000..0c237be
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/ComparisonExpr.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+
+import java.util.List;
+
+public class ComparisonExpr extends Expr {
+ final String mOp;
+ ComparisonExpr(String op, Expr left, Expr right) {
+ super(left, right);
+ mOp = op;
+ }
+
+ @Override
+ protected String computeUniqueKey() {
+ return sUniqueKeyJoiner.join(mOp, super.computeUniqueKey());
+ }
+
+ @Override
+ protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+ return modelAnalyzer.loadPrimitive("boolean");
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ return constructDynamicChildrenDependencies();
+ }
+
+ public String getOp() {
+ return mOp;
+ }
+
+ public Expr getLeft() {
+ return getChildren().get(0);
+ }
+
+ public Expr getRight() {
+ return getChildren().get(1);
+ }
+
+ @Override
+ public boolean isEqualityCheck() {
+ return "==".equals(mOp.trim());
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/Dependency.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/Dependency.java
new file mode 100644
index 0000000..1678af7
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/Dependency.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+public class Dependency {
+ final Expr mDependant;
+ final Expr mOther;
+ final Expr mCondition;
+ final boolean mExpectedOutput;// !
+ // set only if this is conditional. Means it has been resolved so that it can be used in
+ // should get calculations
+ boolean mElevated;
+
+ // this means that trying to calculate the dependant expression w/o
+ // will crash the app unless "Other" has a non-null value
+ boolean mMandatory = false;
+
+ public Dependency(Expr dependant, Expr other) {
+ mDependant = dependant;
+ mOther = other;
+ mCondition = null;
+ mOther.addDependant(this);
+ mExpectedOutput = false;
+ }
+
+ public Dependency(Expr dependant, Expr other, Expr condition, boolean expectedOutput) {
+ mDependant = dependant;
+ mOther = other;
+ mCondition = condition;
+ mOther.addDependant(this);
+ mExpectedOutput = expectedOutput;
+ }
+
+ public void setMandatory(boolean mandatory) {
+ mMandatory = mandatory;
+ }
+
+ public boolean isMandatory() {
+ return mMandatory;
+ }
+
+ public boolean isConditional() {
+ return mCondition != null && !mElevated;
+ }
+
+ public Expr getOther() {
+ return mOther;
+ }
+
+ public Expr getDependant() {
+ return mDependant;
+ }
+
+ public boolean getExpectedOutput() {
+ return mExpectedOutput;
+ }
+
+ public Expr getCondition() {
+ return mCondition;
+ }
+
+ public void elevate() {
+ mElevated = true;
+ }
+
+ public boolean isElevated() {
+ return mElevated;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/Expr.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/Expr.java
new file mode 100644
index 0000000..09b96d8
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/Expr.java
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.List;
+
+abstract public class Expr {
+
+ public static final int NO_ID = -1;
+ protected List<Expr> mChildren = new ArrayList<Expr>();
+
+ // any expression that refers to this. Useful if this expr is duplicate and being replaced
+ private List<Expr> mParents = new ArrayList<Expr>();
+
+ private Boolean mIsDynamic;
+
+ private ModelClass mResolvedType;
+
+ private String mUniqueKey;
+
+ private List<Dependency> mDependencies;
+
+ private List<Dependency> mDependants = Lists.newArrayList();
+
+ private int mId = NO_ID;
+
+ private int mRequirementId = NO_ID;
+
+ // means this expression can directly be invalidated by the user
+ private boolean mCanBeInvalidated = false;
+
+ /**
+ * This set denotes the times when this expression is invalid.
+ * If it is an Identifier expression, it is its index
+ * If it is a composite expression, it is the union of invalid flags of its descendants
+ */
+ private BitSet mInvalidFlags;
+
+ /**
+ * Set when this expression is registered to a model
+ */
+ private ExprModel mModel;
+
+ /**
+ * This set denotes the times when this expression must be read.
+ *
+ * It is the union of invalidation flags of all of its non-conditional dependants.
+ */
+ BitSet mShouldReadFlags;
+
+ BitSet mReadSoFar = new BitSet();// i've read this variable for these flags
+
+ /**
+ * calculated on initialization, assuming all conditionals are true
+ */
+ BitSet mShouldReadWithConditionals;
+
+ private boolean mIsBindingExpression;
+
+ /**
+ * Used by generators when this expression is resolved.
+ */
+ private boolean mRead;
+ private boolean mIsUsed = false;
+
+ Expr(Iterable<Expr> children) {
+ for (Expr expr : children) {
+ mChildren.add(expr);
+ }
+ addParents();
+ }
+
+ Expr(Expr... children) {
+ Collections.addAll(mChildren, children);
+ addParents();
+ }
+
+ public int getId() {
+ Preconditions.checkState(mId != NO_ID, "if getId is called on an expression, it should have"
+ + " and id");
+ return mId;
+ }
+
+ public void setId(int id) {
+ Preconditions.checkState(mId == NO_ID, "ID is already set on " + this);
+ mId = id;
+ }
+
+ public ExprModel getModel() {
+ return mModel;
+ }
+
+ public BitSet getInvalidFlags() {
+ if (mInvalidFlags == null) {
+ mInvalidFlags = resolveInvalidFlags();
+ }
+ return mInvalidFlags;
+ }
+
+ private BitSet resolveInvalidFlags() {
+ BitSet bitSet = new BitSet();
+ if (mCanBeInvalidated) {
+ bitSet.set(getId(), true);
+ }
+ for (Dependency dependency : getDependencies()) {
+ // TODO optional optimization: do not invalidate for conditional flags
+ bitSet.or(dependency.getOther().getInvalidFlags());
+ }
+ return bitSet;
+ }
+
+ public void setBindingExpression(boolean isBindingExpression) {
+ mIsBindingExpression = isBindingExpression;
+ }
+
+ public boolean isBindingExpression() {
+ return mIsBindingExpression;
+ }
+
+ public boolean isObservable() {
+ return getResolvedType().isObservable();
+ }
+
+ public BitSet getShouldReadFlags() {
+ if (mShouldReadFlags == null) {
+ getShouldReadFlagsWithConditionals();
+ mShouldReadFlags = resolveShouldReadFlags();
+ }
+ return mShouldReadFlags;
+ }
+
+ public BitSet getShouldReadFlagsWithConditionals() {
+ if (mShouldReadWithConditionals == null) {
+ mShouldReadWithConditionals = resolveShouldReadWithConditionals();
+ }
+ return mShouldReadWithConditionals;
+ }
+
+ public void setModel(ExprModel model) {
+ mModel = model;
+ }
+
+ private BitSet resolveShouldReadWithConditionals() {
+ // ensure we have invalid flags
+ BitSet bitSet = new BitSet();
+ // if i'm invalid, that DOES NOT mean i should be read :/.
+ if (mIsBindingExpression) {
+ bitSet.or(getInvalidFlags());
+ }
+
+ for (Dependency dependency : getDependants()) {
+ // first traverse non-conditionals because we'll avoid adding conditionals if we are get because of these anyways
+ if (dependency.getCondition() == null) {
+ bitSet.or(dependency.getDependant().getShouldReadFlagsWithConditionals());
+ } else {
+ bitSet.set(dependency.getDependant()
+ .getRequirementFlagIndex(dependency.getExpectedOutput()));
+ }
+ }
+ return bitSet;
+ }
+
+ private BitSet resolveShouldReadFlags() {
+ // ensure we have invalid flags
+ BitSet bitSet = new BitSet();
+ if (isRead()) {
+ return bitSet;
+ }
+ if (mIsBindingExpression) {
+ bitSet.or(getInvalidFlags());
+ }
+ for (Dependency dependency : getDependants()) {
+ final boolean isElevated = unreadElevatedCheck.apply(dependency);
+ if (dependency.isConditional()) {
+ continue; // TODO
+ }
+ if (isElevated) {
+ // if i already have all flags that will require my dependant's predicate to
+ // be read, that means i'm already read thus can avoid adding its conditional
+ // dependency
+ if (!dependency.getDependant().getAllCalculationPaths().areAllPathsSatisfied(
+ mReadSoFar)) {
+ bitSet.set(dependency.getDependant()
+ .getRequirementFlagIndex(dependency.getExpectedOutput()));
+ }
+ } else {
+ bitSet.or(dependency.getDependant().getShouldReadFlags());
+ }
+ }
+ bitSet.andNot(mReadSoFar);
+ // should read w/ conditionals does eleminate for unnecessary re-reads
+ bitSet.and(mShouldReadWithConditionals);
+ return bitSet;
+ }
+
+ Predicate<Dependency> unreadElevatedCheck = new Predicate<Dependency>() {
+ @Override
+ public boolean apply(Dependency input) {
+ return input.isElevated() && !input.getDependant().isRead();
+ }
+ };
+
+ private void addParents() {
+ for (Expr expr : mChildren) {
+ expr.mParents.add(this);
+ }
+ }
+
+ public void onSwappedWith(Expr existing) {
+ for (Expr child : mChildren) {
+ child.onParentSwapped(this, existing);
+ }
+ }
+
+ private void onParentSwapped(Expr oldParent, Expr newParent) {
+ Preconditions.checkState(mParents.remove(oldParent));
+ mParents.add(newParent);
+ }
+
+ public List<Expr> getChildren() {
+ return mChildren;
+ }
+
+ public List<Expr> getParents() {
+ return mParents;
+ }
+
+ /**
+ * Whether the result of this expression can change or not.
+ *
+ * For example, 3 + 5 can not change vs 3 + x may change.
+ *
+ * Default implementations checks children and returns true if any of them returns true
+ *
+ * @return True if the result of this expression may change due to variables
+ */
+ public boolean isDynamic() {
+ if (mIsDynamic == null) {
+ mIsDynamic = isAnyChildDynamic();
+ }
+ return mIsDynamic;
+ }
+
+ private boolean isAnyChildDynamic() {
+ return Iterables.any(mChildren, new Predicate<Expr>() {
+ @Override
+ public boolean apply(Expr input) {
+ return input.isDynamic();
+ }
+ });
+
+ }
+
+ public ModelClass getResolvedType() {
+ if (mResolvedType == null) {
+ // TODO not get instance
+ mResolvedType = resolveType(ModelAnalyzer.getInstance());
+ }
+ return mResolvedType;
+ }
+
+ abstract protected ModelClass resolveType(ModelAnalyzer modelAnalyzer);
+
+ abstract protected List<Dependency> constructDependencies();
+
+ /**
+ * Creates a dependency for each dynamic child. Should work for any expression besides
+ * conditionals.
+ */
+ protected List<Dependency> constructDynamicChildrenDependencies() {
+ List<Dependency> dependencies = new ArrayList<Dependency>();
+ for (Expr node : mChildren) {
+ if (!node.isDynamic()) {
+ continue;
+ }
+ dependencies.add(new Dependency(this, node));
+ }
+ return dependencies;
+ }
+
+ public final List<Dependency> getDependencies() {
+ if (mDependencies == null) {
+ mDependencies = constructDependencies();
+ }
+ return mDependencies;
+ }
+
+ void addDependant(Dependency dependency) {
+ mDependants.add(dependency);
+ }
+
+ public List<Dependency> getDependants() {
+ return mDependants;
+ }
+
+ protected static final String KEY_JOIN = "~";
+ protected static final Joiner sUniqueKeyJoiner = Joiner.on(KEY_JOIN);
+
+ /**
+ * Returns a unique string key that can identify this expression.
+ *
+ * It must take into account any dependencies
+ *
+ * @return A unique identifier for this expression
+ */
+ public final String getUniqueKey() {
+ if (mUniqueKey == null) {
+ mUniqueKey = computeUniqueKey();
+ Preconditions.checkNotNull(mUniqueKey,
+ "if there are no children, you must override computeUniqueKey");
+ Preconditions.checkState(!mUniqueKey.trim().equals(""),
+ "if there are no children, you must override computeUniqueKey");
+ }
+ return mUniqueKey;
+ }
+
+ protected String computeUniqueKey() {
+ return computeChildrenKey();
+ }
+
+ protected final String computeChildrenKey() {
+ return sUniqueKeyJoiner.join(Iterables.transform(mChildren, new Function<Expr, String>() {
+ @Override
+ public String apply(Expr input) {
+ return input.getUniqueKey();
+ }
+ }));
+ }
+
+ public void enableDirectInvalidation() {
+ mCanBeInvalidated = true;
+ }
+
+ public boolean canBeInvalidated() {
+ return mCanBeInvalidated;
+ }
+
+ public void trimShouldReadFlags(BitSet bitSet) {
+ mShouldReadFlags.andNot(bitSet);
+ }
+
+ public boolean isConditional() {
+ return false;
+ }
+
+ public int getRequirementId() {
+ return mRequirementId;
+ }
+
+ public void setRequirementId(int requirementId) {
+ mRequirementId = requirementId;
+ }
+
+ /**
+ * This is called w/ a dependency of mine.
+ * Base method should thr
+ */
+ public int getRequirementFlagIndex(boolean expectedOutput) {
+ Preconditions.checkState(mRequirementId != NO_ID, "If this is an expression w/ conditional"
+ + " dependencies, it must be assigned a requirement ID");
+ return expectedOutput ? mRequirementId + 1 : mRequirementId;
+ }
+
+ public boolean hasId() {
+ return mId != NO_ID;
+ }
+
+ public void markFlagsAsRead(BitSet flags) {
+ mReadSoFar.or(flags);
+ }
+
+ public boolean isRead() {
+ return mRead;
+ }
+
+ public boolean considerElevatingConditionals(Expr justRead) {
+ boolean elevated = false;
+ for (Dependency dependency : mDependencies) {
+ if (dependency.isConditional() && dependency.getCondition() == justRead) {
+ dependency.elevate();
+ elevated = true;
+ }
+ }
+ return elevated;
+ }
+
+ public void invalidateReadFlags() {
+ mShouldReadFlags = null;
+ }
+
+ public boolean hasNestedCannotRead() {
+ if (isRead()) {
+ return false;
+ }
+ if (getShouldReadFlags().isEmpty()) {
+ return true;
+ }
+ return Iterables.any(getDependencies(), hasNestedCannotRead);
+ }
+
+ Predicate<Dependency> hasNestedCannotRead = new Predicate<Dependency>() {
+ @Override
+ public boolean apply(Dependency input) {
+ return input.isConditional() || input.getOther().hasNestedCannotRead();
+ }
+ };
+
+ public boolean markAsReadIfDone() {
+ if (mRead) {
+ return false;
+ }
+ // TODO avoid clone, we can calculate this iteratively
+ BitSet clone = (BitSet) mShouldReadWithConditionals.clone();
+
+ clone.andNot(mReadSoFar);
+ mRead = clone.isEmpty();
+ if (!mRead && !mReadSoFar.isEmpty()) {
+ // check if remaining dependencies can be satisfied w/ existing values
+ // for predicate flags, this expr may already be calculated to get the predicate
+ // to detect them, traverse them later on, see which flags should be calculated to calculate
+ // them. If any of them is completely covered w/ our non-conditional flags, no reason
+ // to add them to the list since we'll already be calculated due to our non-conditional
+ // flags
+
+ for (int i = clone.nextSetBit(0); i != -1; i = clone.nextSetBit(i + 1)) {
+ final Expr expr = mModel.findFlagExpression(i);
+ if (!expr.isConditional()) {
+ continue;
+ }
+ final BitSet readForConditional = expr.findConditionalFlags();
+ // to calculate that conditional, i should've read /readForConditional/ flags
+ // if my read-so-far bits has any common w/ that; that means i would've already
+ // read myself
+ clone.andNot(readForConditional);
+ final BitSet invalidFlags = (BitSet) getInvalidFlags().clone();
+ invalidFlags.andNot(readForConditional);
+ mRead = invalidFlags.isEmpty() || clone.isEmpty();
+ }
+
+ }
+ if (mRead) {
+ mShouldReadFlags = null; // if we've been marked as read, clear should read flags
+ }
+ return mRead;
+ }
+
+ BitSet mConditionalFlags;
+
+ private BitSet findConditionalFlags() {
+ Preconditions.checkState(isConditional(), "should not call this on a non-conditional expr");
+ if (mConditionalFlags == null) {
+ mConditionalFlags = new BitSet();
+ resolveConditionalFlags(mConditionalFlags);
+ }
+ return mConditionalFlags;
+ }
+
+ private void resolveConditionalFlags(BitSet flags) {
+ flags.or(getPredicateInvalidFlags());
+ // if i have only 1 dependency which is conditional, traverse it as well
+ if (getDependants().size() == 1) {
+ final Dependency dependency = getDependants().get(0);
+ if (dependency.getCondition() != null) {
+ flags.or(dependency.getDependant().findConditionalFlags());
+ flags.set(dependency.getDependant()
+ .getRequirementFlagIndex(dependency.getExpectedOutput()));
+ }
+ }
+ }
+
+
+ @Override
+ public String toString() {
+ return getUniqueKey();
+ }
+
+ public BitSet getReadSoFar() {
+ return mReadSoFar;
+ }
+
+ private Node mCalculationPaths = null;
+
+ protected Node getAllCalculationPaths() {
+ if (mCalculationPaths == null) {
+ Node node = new Node();
+ // TODO distant parent w/ conditionals are still not traversed :/
+ if (isConditional()) {
+ node.mBitSet.or(getPredicateInvalidFlags());
+ } else {
+ node.mBitSet.or(getInvalidFlags());
+ }
+ for (Dependency dependency : getDependants()) {
+ final Expr dependant = dependency.getDependant();
+ if (dependency.getCondition() != null) {
+ Node cond = new Node();
+ cond.setConditionFlag(
+ dependant.getRequirementFlagIndex(dependency.getExpectedOutput()));
+ cond.mParents.add(dependant.getAllCalculationPaths());
+ } else {
+ node.mParents.add(dependant.getAllCalculationPaths());
+ }
+ }
+ mCalculationPaths = node;
+ }
+ return mCalculationPaths;
+ }
+
+ public String getDefaultValue() {
+ return ModelAnalyzer.getInstance().getDefaultValue(getResolvedType().toJavaCode());
+ }
+
+ protected BitSet getPredicateInvalidFlags() {
+ throw new IllegalStateException(
+ "must override getPredicateInvalidFlags in " + getClass().getSimpleName());
+ }
+
+ /**
+ * Used by code generation
+ */
+ public boolean shouldReadNow(final Iterable<Expr> justRead) {
+ return !getShouldReadFlags().isEmpty() &&
+ !Iterables.any(getDependencies(), new Predicate<Dependency>() {
+ @Override
+ public boolean apply(Dependency input) {
+ return !(input.getOther().isRead() || (justRead != null && Iterables
+ .contains(justRead, input.getOther())));
+ }
+ });
+ }
+
+ public boolean isEqualityCheck() {
+ return false;
+ }
+
+ public void setIsUsed(boolean isUsed) {
+ mIsUsed = isUsed;
+ }
+
+ public boolean isUsed() {
+ return mIsUsed;
+ }
+
+ public void updateExpr(ModelAnalyzer modelAnalyzer) {
+ for (Expr child : mChildren) {
+ child.updateExpr(modelAnalyzer);
+ }
+ }
+
+ protected String asPackage() {
+ return null;
+ }
+
+ static class Node {
+
+ BitSet mBitSet = new BitSet();
+ List<Node> mParents = new ArrayList<Node>();
+ int mConditionFlag = -1;
+
+ public boolean areAllPathsSatisfied(BitSet readSoFar) {
+ if (mConditionFlag != -1) {
+ return readSoFar.get(mConditionFlag) || mParents.get(0)
+ .areAllPathsSatisfied(readSoFar);
+ } else {
+ final BitSet clone = (BitSet) readSoFar.clone();
+ readSoFar.and(mBitSet);
+ if (!readSoFar.isEmpty()) {
+ return true;
+ }
+ if (mParents.isEmpty()) {
+ return false;
+ }
+ for (Node parent : mParents) {
+ if (!parent.areAllPathsSatisfied(readSoFar)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ public void setConditionFlag(int requirementFlagIndex) {
+ mConditionFlag = requirementFlagIndex;
+ }
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/ExprModel.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/ExprModel.java
new file mode 100644
index 0000000..0f8a935
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/ExprModel.java
@@ -0,0 +1,495 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.util.L;
+import android.databinding.tool.writer.FlagSet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ExprModel {
+
+ Map<String, Expr> mExprMap = new HashMap<String, Expr>();
+
+ List<Expr> mBindingExpressions = new ArrayList<Expr>();
+
+ private int mInvalidateableFieldLimit = 0;
+
+ private int mRequirementIdCount = 0;
+
+ private static final String TRUE_KEY_SUFFIX = "== true";
+ private static final String FALSE_KEY_SUFFIX = "== false";
+
+ /**
+ * Used by code generation. Keeps the list of expressions that are waiting to be evaluated.
+ */
+ private List<Expr> mPendingExpressions;
+
+ /**
+ * Used for converting flags into identifiers while debugging.
+ */
+ private String[] mFlagMapping;
+
+ private BitSet mInvalidateableFlags;
+ private BitSet mConditionalFlags;
+
+ private int mFlagBucketCount;// how many buckets we use to identify flags
+
+ private List<Expr> mObservables;
+
+ private Map<String, String> mImports = new HashMap<String, String>();
+
+ /**
+ * Adds the expression to the list of expressions and returns it.
+ * If it already exists, returns existing one.
+ *
+ * @param expr The new parsed expression
+ * @return The expression itself or another one if the same thing was parsed before
+ */
+ public <T extends Expr> T register(T expr) {
+ T existing = (T) mExprMap.get(expr.getUniqueKey());
+ if (existing != null) {
+ Preconditions.checkState(expr.getParents().isEmpty(),
+ "If an expression already exists, it should've never been added to a parent,"
+ + "if thats the case, somewhere we are creating an expression w/o"
+ + "calling expression model");
+ // tell the expr that it is being swapped so that if it was added to some other expr
+ // as a parent, those can swap their references
+ expr.onSwappedWith(existing);
+ return existing;
+ }
+ mExprMap.put(expr.getUniqueKey(), expr);
+ expr.setModel(this);
+ return expr;
+ }
+
+ public void unregister(Expr expr) {
+ mExprMap.remove(expr.getUniqueKey());
+ }
+
+ public Map<String, Expr> getExprMap() {
+ return mExprMap;
+ }
+
+ public int size() {
+ return mExprMap.size();
+ }
+
+ public ComparisonExpr comparison(String op, Expr left, Expr right) {
+ return register(new ComparisonExpr(op, left, right));
+ }
+
+ public FieldAccessExpr field(Expr parent, String name) {
+ return register(new FieldAccessExpr(parent, name));
+ }
+
+ public FieldAccessExpr observableField(Expr parent, String name) {
+ return register(new FieldAccessExpr(parent, name, true));
+ }
+
+ public SymbolExpr symbol(String text, Class type) {
+ return register(new SymbolExpr(text, type));
+ }
+
+ public TernaryExpr ternary(Expr pred, Expr ifTrue, Expr ifFalse) {
+ return register(new TernaryExpr(pred, ifTrue, ifFalse));
+ }
+
+ public IdentifierExpr identifier(String name) {
+ return register(new IdentifierExpr(name));
+ }
+
+ public StaticIdentifierExpr staticIdentifier(String name) {
+ return register(new StaticIdentifierExpr(name));
+ }
+
+ public MethodCallExpr methodCall(Expr target, String name, List<Expr> args) {
+ return register(new MethodCallExpr(target, name, args));
+ }
+
+ public MathExpr math(Expr left, String op, Expr right) {
+ return register(new MathExpr(left, op, right));
+ }
+
+ public Expr group(Expr grouped) {
+ return register(new GroupExpr(grouped));
+ }
+
+ public Expr resourceExpr(String packageName, String resourceType, String resourceName,
+ List<Expr> args) {
+ return register(new ResourceExpr(packageName, resourceType, resourceName, args));
+ }
+
+ public Expr bracketExpr(Expr variableExpr, Expr argExpr) {
+ return register(new BracketExpr(variableExpr, argExpr));
+ }
+
+ public Expr castExpr(String type, Expr expr) {
+ return register(new CastExpr(type, expr));
+ }
+
+ public List<Expr> getBindingExpressions() {
+ return mBindingExpressions;
+ }
+
+ public void addImport(String alias, String type) {
+ Preconditions.checkState(!mImports.containsKey(alias),
+ "%s has already been defined as %s", alias, type);
+ final StaticIdentifierExpr id = staticIdentifier(alias);
+ L.d("adding import %s as %s klass: %s", type, alias, id.getClass().getSimpleName());
+ id.setUserDefinedType(type);
+ mImports.put(alias, type);
+ }
+
+ public Map<String, String> getImports() {
+ return mImports;
+ }
+
+ /**
+ * The actual thingy that is set on the binding target.
+ *
+ * Input must be already registered
+ */
+ public Expr bindingExpr(Expr bindingExpr) {
+ Preconditions.checkArgument(mExprMap.containsKey(bindingExpr.getUniqueKey()),
+ "Main expression should already be registered");
+ if (!mBindingExpressions.contains(bindingExpr)) {
+ mBindingExpressions.add(bindingExpr);
+ }
+ return bindingExpr;
+ }
+
+ /**
+ * Nodes to which no one depends
+ */
+ public Iterable<Expr> findRootNodes() {
+ return Iterables.filter(mExprMap.values(), new Predicate<Expr>() {
+ @Override
+ public boolean apply(Expr input) {
+ return input.getParents().isEmpty();
+ }
+ });
+ }
+
+ /**
+ * Nodes, which do not depend on any other node
+ */
+ public Iterable<Expr> findLeafNodes() {
+ return Iterables.filter(mExprMap.values(), new Predicate<Expr>() {
+ @Override
+ public boolean apply(Expr input) {
+ return input.getChildren().isEmpty();
+ }
+ });
+ }
+
+ public List<Expr> getObservables() {
+ return mObservables;
+ }
+
+ /**
+ * Give id to each expression. Will be useful if we serialize.
+ */
+ public void seal() {
+ List<Expr> notifiableExpressions = new ArrayList<Expr>();
+ //ensure class analyzer. We need to know observables at this point
+ final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
+
+ ArrayList<Expr> exprs = new ArrayList<Expr>(mBindingExpressions);
+ for (Expr expr: exprs) {
+ expr.updateExpr(modelAnalyzer);
+ }
+
+ int counter = 0;
+ final Iterable<Expr> observables = filterObservables(modelAnalyzer);
+ List<String> flagMapping = Lists.newArrayList();
+ mObservables = Lists.newArrayList();
+ for (Expr expr : observables) {
+ // observables gets initial ids
+ flagMapping.add(expr.getUniqueKey());
+ expr.setId(counter++);
+ mObservables.add(expr);
+ notifiableExpressions.add(expr);
+ L.d("observable %s", expr.getUniqueKey());
+ }
+
+ // non-observable identifiers gets next ids
+ final Iterable<Expr> nonObservableIds = filterNonObservableIds(modelAnalyzer);
+ for (Expr expr : nonObservableIds) {
+ flagMapping.add(expr.getUniqueKey());
+ expr.setId(counter++);
+ notifiableExpressions.add(expr);
+ L.d("non-observable %s", expr.getUniqueKey());
+ }
+
+ // descendents of observables gets following ids
+ for (Expr expr : observables) {
+ for (Expr parent : expr.getParents()) {
+ if (parent.hasId()) {
+ continue;// already has some id, means observable
+ }
+ // only fields earn an id
+ if (parent instanceof FieldAccessExpr) {
+ FieldAccessExpr fae = (FieldAccessExpr) parent;
+ L.d("checking field access expr %s. getter: %s", fae,fae.getGetter());
+ if (fae.isDynamic() && fae.getGetter().canBeInvalidated) {
+ flagMapping.add(parent.getUniqueKey());
+ parent.setId(counter++);
+ notifiableExpressions.add(parent);
+ L.d("notifiable field %s : %s for %s : %s", parent.getUniqueKey(),
+ Integer.toHexString(System.identityHashCode(parent)),
+ expr.getUniqueKey(),
+ Integer.toHexString(System.identityHashCode(expr)));
+ }
+ }
+ }
+ }
+
+ // non-dynamic binding expressions receive some ids so that they can be invalidated
+ for (int i = 0; i < mBindingExpressions.size(); i++) {
+ L.d("[" + i + "] " + mBindingExpressions.get(i));
+ }
+ for (Expr expr : mBindingExpressions) {
+ if (!(expr.isDynamic() || !expr.hasId())) {
+ L.d("Expr " + expr + " is dynamic? " + expr.isDynamic() + ", has ID? " + expr.hasId());
+ }
+ Preconditions.checkState(expr.isDynamic() || !expr.hasId());
+ if (!expr.isDynamic()) {
+ // give it an id for invalidateAll
+ expr.setId(counter ++);
+ notifiableExpressions.add(expr);
+ }
+ }
+
+ for (Expr expr : notifiableExpressions) {
+ expr.enableDirectInvalidation();
+ }
+
+ // make sure all dependencies are resolved to avoid future race conditions
+ for (Expr expr : mExprMap.values()) {
+ expr.getDependencies();
+ }
+
+ mInvalidateableFieldLimit = counter;
+ mInvalidateableFlags = new BitSet();
+ for (int i = 0; i < mInvalidateableFieldLimit; i++) {
+ mInvalidateableFlags.set(i, true);
+ }
+
+ // make sure all dependencies are resolved to avoid future race conditions
+ for (Expr expr : mExprMap.values()) {
+ if (expr.isConditional()) {
+ expr.setRequirementId(counter);
+ flagMapping.add(expr.getUniqueKey() + FALSE_KEY_SUFFIX);
+ flagMapping.add(expr.getUniqueKey() + TRUE_KEY_SUFFIX);
+ counter += 2;
+ }
+ }
+ mConditionalFlags = new BitSet();
+ for (int i = mInvalidateableFieldLimit; i < counter; i++) {
+ mConditionalFlags.set(i, true);
+ }
+
+ mRequirementIdCount = (counter - mInvalidateableFieldLimit) / 2;
+
+ // everybody gets an id
+ for (Map.Entry<String, Expr> entry : mExprMap.entrySet()) {
+ final Expr value = entry.getValue();
+ if (!value.hasId()) {
+ value.setId(counter++);
+ }
+ }
+ mFlagMapping = new String[flagMapping.size()];
+ flagMapping.toArray(mFlagMapping);
+
+ for (Expr expr : mExprMap.values()) {
+ expr.getShouldReadFlagsWithConditionals();
+ }
+
+ for (Expr expr : mExprMap.values()) {
+ // ensure all types are calculated
+ expr.getResolvedType();
+ }
+
+ mFlagBucketCount = 1 + (getTotalFlagCount() / FlagSet.sBucketSize);
+ }
+
+ public int getFlagBucketCount() {
+ return mFlagBucketCount;
+ }
+
+ public int getTotalFlagCount() {
+ return mRequirementIdCount * 2 + mInvalidateableFieldLimit;
+ }
+
+ public int getInvalidateableFieldLimit() {
+ return mInvalidateableFieldLimit;
+ }
+
+ public String[] getFlagMapping() {
+ return mFlagMapping;
+ }
+
+ public String getFlag(int id) {
+ return mFlagMapping[id];
+ }
+
+ private Iterable<Expr> filterNonObservableIds(final ModelAnalyzer modelAnalyzer) {
+ return Iterables.filter(mExprMap.values(), new Predicate<Expr>() {
+ @Override
+ public boolean apply(Expr input) {
+ return input instanceof IdentifierExpr
+ && !input.hasId()
+ && !input.isObservable()
+ && input.isDynamic();
+ }
+ });
+ }
+
+ private Iterable<Expr> filterObservables(final ModelAnalyzer modelAnalyzer) {
+ return Iterables.filter(mExprMap.values(), new Predicate<Expr>() {
+ @Override
+ public boolean apply(Expr input) {
+ return input.isObservable();
+ }
+ });
+ }
+
+ public List<Expr> getPendingExpressions() {
+ if (mPendingExpressions == null) {
+ mPendingExpressions = Lists.newArrayList();
+ for (Expr expr : mExprMap.values()) {
+ if (!expr.isRead() && expr.isDynamic()) {
+ mPendingExpressions.add(expr);
+ }
+ }
+ }
+ return mPendingExpressions;
+ }
+
+ public boolean markBitsRead() {
+ L.d("marking bits as done");
+ // each has should read flags, we set them back on them
+ for (Expr expr : filterShouldRead(getPendingExpressions())) {
+ expr.markFlagsAsRead(expr.getShouldReadFlags());
+ }
+ return pruneDone();
+ }
+
+ private boolean pruneDone() {
+ boolean marked = true;
+ List<Expr> markedAsReadList = Lists.newArrayList();
+ while (marked) {
+ marked = false;
+ for (Expr expr : mExprMap.values()) {
+ if (expr.isRead()) {
+ continue;
+ }
+ if (expr.markAsReadIfDone()) {
+ L.d("marked %s as read ", expr.getUniqueKey());
+ marked = true;
+ markedAsReadList.add(expr);
+ }
+
+ }
+ }
+ boolean elevated = false;
+ for (Expr markedAsRead : markedAsReadList) {
+ for (Dependency dependency : markedAsRead.getDependants()) {
+ if (dependency.getDependant().considerElevatingConditionals(markedAsRead)) {
+ elevated = true;
+ }
+ }
+ }
+ if (elevated) {
+ // some conditionals are elevated. We should re-calculate flags
+ for (Expr expr : getPendingExpressions()) {
+ if (!expr.isRead()) {
+ expr.invalidateReadFlags();
+ }
+ }
+ mPendingExpressions = null;
+ }
+ return elevated;
+ }
+
+ public static Iterable<Expr> filterShouldRead(Iterable<Expr> exprs) {
+ return toCollection(Iterables.filter(exprs, sShouldReadPred));
+ }
+
+ public static List<Expr> toCollection(Iterable<Expr> iterable) {
+ return Arrays.asList(Iterables.toArray(iterable, Expr.class));
+ }
+
+ private static final Predicate<Expr> sShouldReadPred = new Predicate<Expr>() {
+ @Override
+ public boolean apply(final Expr expr) {
+ return !expr.getShouldReadFlags().isEmpty() && !Iterables.any(
+ expr.getDependencies(), new Predicate<Dependency>() {
+ @Override
+ public boolean apply(Dependency dependency) {
+ final boolean result = dependency.isConditional() ||
+ dependency.getOther().hasNestedCannotRead();
+ return result;
+ }
+ });
+ }
+ };
+
+ private static final Predicate<Expr> sReadNowPred = new Predicate<Expr>() {
+ @Override
+ public boolean apply(Expr input) {
+ return !input.getShouldReadFlags().isEmpty() &&
+ !Iterables.any(input.getDependencies(), new Predicate<Dependency>() {
+ @Override
+ public boolean apply(Dependency input) {
+ return !input.getOther().isRead();
+ }
+ });
+ }
+ };
+
+ public Expr findFlagExpression(int flag) {
+ final String key = mFlagMapping[flag];
+ if (mExprMap.containsKey(key)) {
+ return mExprMap.get(key);
+ }
+ int falseIndex = key.indexOf(FALSE_KEY_SUFFIX);
+ if (falseIndex > -1) {
+ final String trimmed = key.substring(0, falseIndex);
+ return mExprMap.get(trimmed);
+ }
+ int trueIndex = key.indexOf(TRUE_KEY_SUFFIX);
+ if (trueIndex > -1) {
+ final String trimmed = key.substring(0, trueIndex);
+ return mExprMap.get(trimmed);
+ }
+ Preconditions.checkArgument(false, "cannot find expression for flag %d", flag);
+ return null;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java
new file mode 100644
index 0000000..754cca8
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import android.databinding.tool.reflection.Callable;
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.util.L;
+
+import java.util.List;
+
+public class FieldAccessExpr extends Expr {
+ String mName;
+ Callable mGetter;
+ final boolean mIsObservableField;
+
+ FieldAccessExpr(Expr parent, String name) {
+ super(parent);
+ mName = name;
+ mIsObservableField = false;
+ }
+
+ FieldAccessExpr(Expr parent, String name, boolean isObservableField) {
+ super(parent);
+ mName = name;
+ mIsObservableField = isObservableField;
+ }
+
+ public Expr getChild() {
+ return getChildren().get(0);
+ }
+
+ public Callable getGetter() {
+ if (mGetter == null) {
+ getResolvedType();
+ }
+ return mGetter;
+ }
+
+ @Override
+ public boolean isDynamic() {
+ if (!getChild().isDynamic()) {
+ return false;
+ }
+ if (mGetter == null) {
+ getResolvedType();
+ }
+ // maybe this is just a final field in which case cannot be notified as changed
+ return mGetter.type != Callable.Type.FIELD || mGetter.isDynamic;
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ final List<Dependency> dependencies = constructDynamicChildrenDependencies();
+ for (Dependency dependency : dependencies) {
+ if (dependency.getOther() == getChild()) {
+ dependency.setMandatory(true);
+ }
+ }
+ return dependencies;
+ }
+
+ @Override
+ protected String computeUniqueKey() {
+ if (mIsObservableField) {
+ return sUniqueKeyJoiner.join(mName, "..", super.computeUniqueKey());
+ }
+ return sUniqueKeyJoiner.join(mName, ".", super.computeUniqueKey());
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public void updateExpr(ModelAnalyzer modelAnalyzer) {
+ resolveType(modelAnalyzer);
+ super.updateExpr(modelAnalyzer);
+ }
+
+ @Override
+ protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+ if (mGetter == null) {
+ Expr child = getChild();
+ child.resolveType(modelAnalyzer);
+ boolean isStatic = child instanceof StaticIdentifierExpr;
+ ModelClass resolvedType = child.getResolvedType();
+ L.d("resolving %s. Resolved type: %s", this, resolvedType);
+
+ mGetter = resolvedType.findGetterOrField(mName, isStatic);
+ if (mGetter.resolvedType.isObservableField()) {
+ // Make this the ".get()" and add an extra field access for the observable field
+ child.getParents().remove(this);
+ getChildren().remove(child);
+
+ FieldAccessExpr observableField = getModel().observableField(child, mName);
+ observableField.mGetter = mGetter;
+
+ getChildren().add(observableField);
+ observableField.getParents().add(this);
+ mGetter = mGetter.resolvedType.findGetterOrField("get", false);
+ mName = "";
+ }
+ }
+ return mGetter.resolvedType;
+ }
+
+ @Override
+ protected String asPackage() {
+ String parentPackage = getChild().asPackage();
+ return parentPackage == null ? null : parentPackage + "." + mName;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/GroupExpr.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/GroupExpr.java
new file mode 100644
index 0000000..8af1d68
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/GroupExpr.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+
+import java.util.List;
+
+public class GroupExpr extends Expr {
+ public GroupExpr(Expr wrapped) {
+ super(wrapped);
+ }
+
+ @Override
+ protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+ return getWrapped().resolveType(modelAnalyzer);
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ return getWrapped().constructDependencies();
+ }
+
+ public Expr getWrapped() {
+ return getChildren().get(0);
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/IdentifierExpr.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/IdentifierExpr.java
new file mode 100644
index 0000000..5207485
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/IdentifierExpr.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.util.L;
+
+import java.util.List;
+
+public class IdentifierExpr extends Expr {
+ String mName;
+ String mUserDefinedType;
+ IdentifierExpr(String name) {
+ mName = name;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * If this is root, its type should be set while parsing the XML document
+ * @param userDefinedType The type of this identifier
+ */
+ public void setUserDefinedType(String userDefinedType) {
+ mUserDefinedType = userDefinedType;
+ }
+
+ @Override
+ protected String computeUniqueKey() {
+ return sUniqueKeyJoiner.join(mName, super.computeUniqueKey());
+ }
+
+ public String getUserDefinedType() {
+ return mUserDefinedType;
+ }
+
+ public String getExpandedUserDefinedType(ModelAnalyzer modelAnalyzer) {
+ Preconditions.checkNotNull(mUserDefinedType,
+ "Identifiers must have user defined types from the XML file. %s is missing it",
+ mName);
+ final String expanded = modelAnalyzer
+ .applyImports(mUserDefinedType, getModel().getImports());
+ L.d("expanded version of %s is %s", mUserDefinedType, expanded);
+ return expanded;
+ }
+
+ @Override
+ public boolean isDynamic() {
+ return true;
+ }
+
+ @Override
+ protected ModelClass resolveType(final ModelAnalyzer modelAnalyzer) {
+ Preconditions.checkNotNull(mUserDefinedType,
+ "Identifiers must have user defined types from the XML file. %s is missing it", mName);
+ return modelAnalyzer.findClass(mUserDefinedType, getModel().getImports());
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ return Lists.newArrayList();
+ }
+
+ @Override
+ protected String asPackage() {
+ return mUserDefinedType == null ? mName : null;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/MathExpr.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/MathExpr.java
new file mode 100644
index 0000000..fbd0b3d
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/MathExpr.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+
+import java.util.List;
+
+public class MathExpr extends Expr {
+ final String mOp;
+ MathExpr(Expr left, String op, Expr right) {
+ super(left, right);
+ mOp = op;
+ }
+
+ @Override
+ protected String computeUniqueKey() {
+ return sUniqueKeyJoiner.join(getLeft().getUniqueKey(), mOp, getRight().getUniqueKey());
+ }
+
+ @Override
+ protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+ if ("+".equals(mOp)) {
+ // TODO we need upper casting etc.
+ if (getLeft().getResolvedType().isString()
+ || getRight().getResolvedType().isString()) {
+ return modelAnalyzer.findClass(String.class);
+ }
+ }
+ return modelAnalyzer.findCommonParentOf(getLeft().getResolvedType(),
+ getRight().getResolvedType());
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ return constructDynamicChildrenDependencies();
+ }
+
+ public String getOp() {
+ return mOp;
+ }
+
+ public Expr getLeft() {
+ return getChildren().get(0);
+ }
+
+ public Expr getRight() {
+ return getChildren().get(1);
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java
new file mode 100644
index 0000000..491adc8
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import com.google.common.collect.Iterables;
+
+import android.databinding.tool.reflection.Callable;
+import android.databinding.tool.reflection.Callable.Type;
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.ModelMethod;
+import android.databinding.tool.util.L;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class MethodCallExpr extends Expr {
+ final String mName;
+
+ Callable mGetter;
+
+ MethodCallExpr(Expr target, String name, List<Expr> args) {
+ super(Iterables.concat(Arrays.asList(target), args));
+ mName = name;
+ }
+
+ @Override
+ public void updateExpr(ModelAnalyzer modelAnalyzer) {
+ resolveType(modelAnalyzer);
+ super.updateExpr(modelAnalyzer);
+ }
+
+ @Override
+ protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+ if (mGetter == null) {
+ List<ModelClass> args = new ArrayList<ModelClass>();
+ for (Expr expr : getArgs()) {
+ args.add(expr.getResolvedType());
+ }
+
+ Expr target = getTarget();
+ boolean isStatic = target instanceof StaticIdentifierExpr;
+ ModelMethod method = target.getResolvedType().getMethod(mName, args, isStatic);
+ if (method == null) {
+ String message = "cannot find method '" + mName + "' in class " +
+ target.getResolvedType().toJavaCode();
+ IllegalArgumentException e = new IllegalArgumentException(message);
+ L.e(e, "cannot find method %s in class %s", mName,
+ target.getResolvedType().toJavaCode());
+ throw e;
+ }
+ mGetter = new Callable(Type.METHOD, method.getName(), method.getReturnType(args), true,
+ false);
+ }
+ return mGetter.resolvedType;
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ final List<Dependency> dependencies = constructDynamicChildrenDependencies();
+ for (Dependency dependency : dependencies) {
+ if (dependency.getOther() == getTarget()) {
+ dependency.setMandatory(true);
+ }
+ }
+ return dependencies;
+ }
+
+ @Override
+ protected String computeUniqueKey() {
+ return sUniqueKeyJoiner.join(getTarget().computeUniqueKey(), mName,
+ super.computeUniqueKey());
+ }
+
+ public Expr getTarget() {
+ return getChildren().get(0);
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public List<Expr> getArgs() {
+ return getChildren().subList(1, getChildren().size());
+ }
+
+ public Callable getGetter() {
+ return mGetter;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/ResourceExpr.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/ResourceExpr.java
new file mode 100644
index 0000000..66391034
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/ResourceExpr.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.tool.expr;
+
+import com.google.common.collect.ImmutableMap;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.writer.WriterPackage;
+
+import java.util.List;
+import java.util.Map;
+
+public class ResourceExpr extends Expr {
+
+ private final static Map<String, String> RESOURCE_TYPE_TO_R_OBJECT =
+ ImmutableMap.<String, String>builder()
+ .put("colorStateList", "color ")
+ .put("dimenOffset", "dimen ")
+ .put("dimenSize", "dimen ")
+ .put("intArray", "array ")
+ .put("stateListAnimator", "animator ")
+ .put("stringArray", "array ")
+ .put("typedArray", "array")
+ .build();
+
+ // lazily initialized
+ private Map<String, ModelClass> mResourceToTypeMapping;
+
+ protected final String mPackage;
+
+ protected final String mResourceType;
+
+ protected final String mResourceId;
+
+ public ResourceExpr(String packageName, String resourceType, String resourceName,
+ List<Expr> args) {
+ super(args);
+ if ("android".equals(packageName)) {
+ mPackage = "android.";
+ } else {
+ mPackage = "";
+ }
+ mResourceType = resourceType;
+ mResourceId = resourceName;
+ }
+
+ private Map<String, ModelClass> getResourceToTypeMapping(ModelAnalyzer modelAnalyzer) {
+ if (mResourceToTypeMapping == null) {
+ final Map<String, String> imports = getModel().getImports();
+ mResourceToTypeMapping = ImmutableMap.<String, ModelClass>builder()
+ .put("anim", modelAnalyzer.findClass("android.view.animation.Animation",
+ imports))
+ .put("animator", modelAnalyzer.findClass("android.animation.Animator",
+ imports))
+ .put("colorStateList",
+ modelAnalyzer.findClass("android.content.res.ColorStateList",
+ imports))
+ .put("drawable", modelAnalyzer.findClass("android.graphics.drawable.Drawable",
+ imports))
+ .put("stateListAnimator",
+ modelAnalyzer.findClass("android.animation.StateListAnimator",
+ imports))
+ .put("transition", modelAnalyzer.findClass("android.transition.Transition",
+ imports))
+ .put("typedArray", modelAnalyzer.findClass("android.content.res.TypedArray",
+ imports))
+ .put("interpolator",
+ modelAnalyzer.findClass("android.view.animation.Interpolator", imports))
+ .put("bool", modelAnalyzer.findClass(boolean.class))
+ .put("color", modelAnalyzer.findClass(int.class))
+ .put("dimenOffset", modelAnalyzer.findClass(int.class))
+ .put("dimenSize", modelAnalyzer.findClass(int.class))
+ .put("id", modelAnalyzer.findClass(int.class))
+ .put("integer", modelAnalyzer.findClass(int.class))
+ .put("layout", modelAnalyzer.findClass(int.class))
+ .put("dimen", modelAnalyzer.findClass(float.class))
+ .put("fraction", modelAnalyzer.findClass(float.class))
+ .put("intArray", modelAnalyzer.findClass(int[].class))
+ .put("string", modelAnalyzer.findClass(String.class))
+ .put("stringArray", modelAnalyzer.findClass(String[].class))
+ .build();
+ }
+ return mResourceToTypeMapping;
+ }
+
+ @Override
+ protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+ final Map<String, ModelClass> mapping = getResourceToTypeMapping(
+ modelAnalyzer);
+ final ModelClass modelClass = mapping.get(mResourceType);
+ if (modelClass != null) {
+ return modelClass;
+ }
+ if ("plurals".equals(mResourceType)) {
+ if (getChildren().isEmpty()) {
+ return modelAnalyzer.findClass(int.class);
+ } else {
+ return modelAnalyzer.findClass(String.class);
+ }
+ }
+ return modelAnalyzer.findClass(mResourceType, getModel().getImports());
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ return constructDynamicChildrenDependencies();
+ }
+
+ @Override
+ protected String computeUniqueKey() {
+ String base;
+ if (mPackage == null) {
+ base = "@" + mResourceType + "/" + mResourceId;
+ } else {
+ base = "@" + "android:" + mResourceType + "/" + mResourceId;
+ }
+ return sUniqueKeyJoiner.join(base, computeChildrenKey());
+ }
+
+ public String getResourceId() {
+ return mPackage + "R." + getResourceObject() + "." + mResourceId;
+ }
+
+ public String toJava() {
+ final String context = "getRoot().getContext()";
+ final String resources = "getRoot().getResources()";
+ final String resourceName = mPackage + "R." + getResourceObject() + "." + mResourceId;
+ if ("anim".equals(mResourceType)) return "android.view.animation.AnimationUtils.loadAnimation(" + context + ", " + resourceName + ")";
+ if ("animator".equals(mResourceType)) return "android.animation.AnimatorInflater.loadAnimator(" + context + ", " + resourceName + ")";
+ if ("bool".equals(mResourceType)) return resources + ".getBoolean(" + resourceName + ")";
+ if ("color".equals(mResourceType)) return resources + ".getColor(" + resourceName + ")";
+ if ("colorStateList".equals(mResourceType)) return resources + ".getColorStateList(" + resourceName + ")";
+ if ("dimen".equals(mResourceType)) return resources + ".getDimension(" + resourceName + ")";
+ if ("dimenOffset".equals(mResourceType)) return resources + ".getDimensionPixelOffset(" + resourceName + ")";
+ if ("dimenSize".equals(mResourceType)) return resources + ".getDimensionPixelSize(" + resourceName + ")";
+ if ("drawable".equals(mResourceType)) return resources + ".getDrawable(" + resourceName + ")";
+ if ("fraction".equals(mResourceType)) {
+ String base = getChildCode(0, "1");
+ String pbase = getChildCode(1, "1");
+ return resources + ".getFraction(" + resourceName + ", " + base + ", " + pbase +
+ ")";
+ }
+ if ("id".equals(mResourceType)) return resourceName;
+ if ("intArray".equals(mResourceType)) return resources + ".getIntArray(" + resourceName + ")";
+ if ("integer".equals(mResourceType)) return resources + ".getInteger(" + resourceName + ")";
+ if ("interpolator".equals(mResourceType)) return "android.view.animation.AnimationUtils.loadInterpolator(" + context + ", " + resourceName + ")";
+ if ("layout".equals(mResourceType)) return resourceName;
+ if ("plurals".equals(mResourceType)) {
+ if (getChildren().isEmpty()) {
+ return resourceName;
+ } else {
+ return makeParameterCall(resourceName, "getQuantityString");
+ }
+ }
+ if ("stateListAnimator".equals(mResourceType)) return "android.animation.AnimatorInflater.loadStateListAnimator(" + context + ", " + resourceName + ")";
+ if ("string".equals(mResourceType)) return makeParameterCall(resourceName, "getString");
+ if ("stringArray".equals(mResourceType)) return resources + ".getStringArray(" + resourceName + ")";
+ if ("transition".equals(mResourceType)) return "android.transition.TransitionInflater.from(" + context + ").inflateTransition(" + resourceName + ")";
+ if ("typedArray".equals(mResourceType)) return resources + ".obtainTypedArray(" + resourceName + ")";
+ final String property = Character.toUpperCase(mResourceType.charAt(0)) +
+ mResourceType.substring(1);
+ return resources + ".get" + property + "(" + resourceName + ")";
+
+ }
+
+ private String getChildCode(int childIndex, String defaultValue) {
+ if (getChildren().size() <= childIndex) {
+ return defaultValue;
+ } else {
+ return WriterPackage.toCode(getChildren().get(childIndex), false).generate();
+ }
+ }
+
+ private String makeParameterCall(String resourceName, String methodCall) {
+ StringBuilder sb = new StringBuilder("getRoot().getResources().");
+ sb.append(methodCall).append("(").append(resourceName);
+ for (Expr expr : getChildren()) {
+ sb.append(", ").append(WriterPackage.toCode(expr, false).generate());
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+
+ private String getResourceObject() {
+ String rFileObject = RESOURCE_TYPE_TO_R_OBJECT.get(mResourceType);
+ if (rFileObject == null) {
+ rFileObject = mResourceType;
+ }
+ return rFileObject;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/StaticIdentifierExpr.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/StaticIdentifierExpr.java
new file mode 100644
index 0000000..8ca5128
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/StaticIdentifierExpr.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+public class StaticIdentifierExpr extends IdentifierExpr {
+
+ StaticIdentifierExpr(String name) {
+ super(name);
+ }
+
+ @Override
+ public boolean isObservable() {
+ return false;
+ }
+
+ @Override
+ public boolean isDynamic() {
+ return false;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/SymbolExpr.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/SymbolExpr.java
new file mode 100644
index 0000000..9e03d2f
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/SymbolExpr.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import com.google.common.collect.Lists;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+
+import java.util.List;
+
+public class SymbolExpr extends Expr {
+ String mText;
+ Class mType;
+
+ SymbolExpr(String text, Class type) {
+ super();
+ mText = text;
+ mType = type;
+ }
+
+ public String getText() {
+ return mText;
+ }
+
+ @Override
+ protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+ return modelAnalyzer.findClass(mType);
+ }
+
+ @Override
+ protected String computeUniqueKey() {
+ return mText;
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ return Lists.newArrayList();
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/TernaryExpr.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/TernaryExpr.java
new file mode 100644
index 0000000..02e5cd7
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/TernaryExpr.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import com.google.common.collect.Lists;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+
+import java.util.BitSet;
+import java.util.List;
+
+public class TernaryExpr extends Expr {
+ TernaryExpr(Expr pred, Expr ifTrue, Expr ifFalse) {
+ super(pred, ifTrue, ifFalse);
+ }
+
+ public Expr getPred() {
+ return getChildren().get(0);
+ }
+
+ public Expr getIfTrue() {
+ return getChildren().get(1);
+ }
+
+ public Expr getIfFalse() {
+ return getChildren().get(2);
+ }
+
+ @Override
+ protected String computeUniqueKey() {
+ return "?:" + super.computeUniqueKey();
+ }
+
+ @Override
+ protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+ return modelAnalyzer.findCommonParentOf(getIfTrue().getResolvedType(),
+ getIfFalse().getResolvedType());
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ List<Dependency> deps = Lists.newArrayList();
+ Expr predExpr = getPred();
+ if (predExpr.isDynamic()) {
+ final Dependency pred = new Dependency(this, predExpr);
+ pred.setMandatory(true);
+ deps.add(pred);
+ }
+ Expr ifTrueExpr = getIfTrue();
+ if (ifTrueExpr.isDynamic()) {
+ deps.add(new Dependency(this, ifTrueExpr, predExpr, true));
+ }
+ Expr ifFalseExpr = getIfFalse();
+ if (ifFalseExpr.isDynamic()) {
+ deps.add(new Dependency(this, ifFalseExpr, predExpr, false));
+ }
+ return deps;
+ }
+
+ @Override
+ protected BitSet getPredicateInvalidFlags() {
+ return getPred().getInvalidFlags();
+ }
+
+ @Override
+ public boolean isConditional() {
+ return true;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/Callable.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/Callable.java
new file mode 100644
index 0000000..8883124
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/Callable.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.tool.reflection;
+
+public class Callable {
+
+ public static enum Type {
+ METHOD,
+ FIELD
+ }
+
+ public final Type type;
+
+ public final String name;
+
+ public final ModelClass resolvedType;
+
+ public final boolean isDynamic;
+
+ public final boolean canBeInvalidated;
+
+ public Callable(Type type, String name, ModelClass resolvedType, boolean isDynamic,
+ boolean canBeInvalidated) {
+ this.type = type;
+ this.name = name;
+ this.resolvedType = resolvedType;
+ this.isDynamic = isDynamic;
+ this.canBeInvalidated = canBeInvalidated;
+ }
+
+ public String getTypeCodeName() {
+ return resolvedType.toJavaCode();
+ }
+
+ @Override
+ public String toString() {
+ return "Callable{" +
+ "type=" + type +
+ ", name='" + name + '\'' +
+ ", resolvedType=" + resolvedType +
+ ", isDynamic=" + isDynamic +
+ ", canBeInvalidated=" + canBeInvalidated +
+ '}';
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java
new file mode 100644
index 0000000..187b9dc
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.tool.reflection;
+
+import com.google.common.base.Preconditions;
+
+import android.databinding.tool.reflection.annotation.AnnotationAnalyzer;
+import android.databinding.tool.util.L;
+
+import java.util.Map;
+
+import javax.annotation.processing.ProcessingEnvironment;
+
+/**
+ * This is the base class for several implementations of something that
+ * acts like a ClassLoader. Different implementations work with the Annotation
+ * Processor, ClassLoader, and an Android Studio plugin.
+ */
+public abstract class ModelAnalyzer {
+
+ public static final String[] LIST_CLASS_NAMES = {
+ "java.util.List",
+ "android.util.SparseArray",
+ "android.util.SparseBooleanArray",
+ "android.util.SparseIntArray",
+ "android.util.SparseLongArray",
+ "android.util.LongSparseArray",
+ "android.support.v4.util.LongSparseArray",
+ };
+
+ public static final String MAP_CLASS_NAME = "java.util.Map";
+
+ public static final String STRING_CLASS_NAME = "java.lang.String";
+
+ public static final String OBJECT_CLASS_NAME = "java.lang.Object";
+
+ public static final String OBSERVABLE_CLASS_NAME = "android.databinding.Observable";
+
+ public static final String OBSERVABLE_LIST_CLASS_NAME = "android.databinding.ObservableList";
+
+ public static final String OBSERVABLE_MAP_CLASS_NAME = "android.databinding.ObservableMap";
+
+ public static final String[] OBSERVABLE_FIELDS = {
+ "android.databinding.ObservableBoolean",
+ "android.databinding.ObservableByte",
+ "android.databinding.ObservableChar",
+ "android.databinding.ObservableShort",
+ "android.databinding.ObservableInt",
+ "android.databinding.ObservableLong",
+ "android.databinding.ObservableFloat",
+ "android.databinding.ObservableDouble",
+ "android.databinding.ObservableField",
+ };
+
+ public static final String VIEW_DATA_BINDING =
+ "android.databinding.ViewDataBinding";
+
+ public static final String VIEW_STUB_CLASS_NAME = "android.view.ViewStub";
+
+ private ModelClass[] mListTypes;
+ private ModelClass mMapType;
+ private ModelClass mStringType;
+ private ModelClass mObjectType;
+ private ModelClass mObservableType;
+ private ModelClass mObservableListType;
+ private ModelClass mObservableMapType;
+ private ModelClass[] mObservableFieldTypes;
+ private ModelClass mViewBindingType;
+ private ModelClass mViewStubType;
+
+ private static ModelAnalyzer sAnalyzer;
+
+ protected void setInstance(ModelAnalyzer analyzer) {
+ sAnalyzer = analyzer;
+ }
+
+ public ModelClass findCommonParentOf(ModelClass modelClass1,
+ ModelClass modelClass2) {
+ ModelClass curr = modelClass1;
+ while (curr != null && !curr.isAssignableFrom(modelClass2)) {
+ curr = curr.getSuperclass();
+ }
+ if (curr == null) {
+ ModelClass primitive1 = modelClass1.unbox();
+ ModelClass primitive2 = modelClass2.unbox();
+ if (!modelClass1.equals(primitive1) || !modelClass2.equals(primitive2)) {
+ return findCommonParentOf(primitive1, primitive2);
+ }
+ }
+ Preconditions.checkNotNull(curr,
+ "must be able to find a common parent for " + modelClass1 + " and " + modelClass2);
+ return curr;
+
+ }
+
+ public abstract ModelClass loadPrimitive(String className);
+
+ public static ModelAnalyzer getInstance() {
+ return sAnalyzer;
+ }
+
+ public static void setProcessingEnvironment(ProcessingEnvironment processingEnvironment) {
+ if (sAnalyzer != null) {
+ throw new IllegalStateException("processing env is already created, you cannot "
+ + "change class loader after that");
+ }
+ L.d("setting processing env to %s", processingEnvironment);
+ AnnotationAnalyzer annotationAnalyzer = new AnnotationAnalyzer(processingEnvironment);
+ sAnalyzer = annotationAnalyzer;
+ }
+
+ /**
+ * Takes a raw className (potentially w/ generics and arrays) and expands definitions using
+ * the import statements.
+ * <p>
+ * For instance, this allows user to define variables
+ * <variable type="User" name="user"/>
+ * if they previously imported User.
+ * <import name="com.example.User"/>
+ */
+ public String applyImports(String className, Map<String, String> imports) {
+ className = className.trim();
+ int numDimensions = 0;
+ String generic = null;
+ // handle array
+ while (className.endsWith("[]")) {
+ numDimensions++;
+ className = className.substring(0, className.length() - 2);
+ }
+ // handle generics
+ final int lastCharIndex = className.length() - 1;
+ if ('>' == className.charAt(lastCharIndex)) {
+ // has generic.
+ int open = className.indexOf('<');
+ if (open == -1) {
+ L.e("un-matching generic syntax for %s", className);
+ return className;
+ }
+ generic = applyImports(className.substring(open + 1, lastCharIndex), imports);
+ className = className.substring(0, open);
+ }
+ int dotIndex = className.indexOf('.');
+ final String qualifier;
+ final String rest;
+ if (dotIndex == -1) {
+ qualifier = className;
+ rest = null;
+ } else {
+ qualifier = className.substring(0, dotIndex);
+ rest = className.substring(dotIndex); // includes dot
+ }
+ final String expandedQualifier = imports.get(qualifier);
+ String result;
+ if (expandedQualifier != null) {
+ result = rest == null ? expandedQualifier : expandedQualifier + rest;
+ } else {
+ result = className; // no change
+ }
+ // now append back dimension and generics
+ if (generic != null) {
+ result = result + "<" + applyImports(generic, imports) + ">";
+ }
+ while (numDimensions-- > 0) {
+ result = result + "[]";
+ }
+ return result;
+ }
+
+ public String getDefaultValue(String className) {
+ if ("int".equals(className)) {
+ return "0";
+ }
+ if ("short".equals(className)) {
+ return "0";
+ }
+ if ("long".equals(className)) {
+ return "0L";
+ }
+ if ("float".equals(className)) {
+ return "0f";
+ }
+ if ("double".equals(className)) {
+ return "0.0";
+ }
+ if ("boolean".equals(className)) {
+ return "false";
+ }
+ if ("char".equals(className)) {
+ return "'\\u0000'";
+ }
+ if ("byte".equals(className)) {
+ return "0";
+ }
+ return "null";
+ }
+
+ public abstract ModelClass findClass(String className, Map<String, String> imports);
+
+ public abstract ModelClass findClass(Class classType);
+
+ public abstract TypeUtil createTypeUtil();
+
+ ModelClass[] getListTypes() {
+ if (mListTypes == null) {
+ mListTypes = new ModelClass[LIST_CLASS_NAMES.length];
+ for (int i = 0; i < mListTypes.length; i++) {
+ final ModelClass modelClass = findClass(LIST_CLASS_NAMES[i], null);
+ if (modelClass != null) {
+ mListTypes[i] = modelClass.erasure();
+ }
+ }
+ }
+ return mListTypes;
+ }
+
+ public ModelClass getMapType() {
+ if (mMapType == null) {
+ mMapType = loadClassErasure(MAP_CLASS_NAME);
+ }
+ return mMapType;
+ }
+
+ ModelClass getStringType() {
+ if (mStringType == null) {
+ mStringType = findClass(STRING_CLASS_NAME, null);
+ }
+ return mStringType;
+ }
+
+ ModelClass getObjectType() {
+ if (mObjectType == null) {
+ mObjectType = findClass(OBJECT_CLASS_NAME, null);
+ }
+ return mObjectType;
+ }
+
+ ModelClass getObservableType() {
+ if (mObservableType == null) {
+ mObservableType = findClass(OBSERVABLE_CLASS_NAME, null);
+ }
+ return mObservableType;
+ }
+
+ ModelClass getObservableListType() {
+ if (mObservableListType == null) {
+ mObservableListType = loadClassErasure(OBSERVABLE_LIST_CLASS_NAME);
+ }
+ return mObservableListType;
+ }
+
+ ModelClass getObservableMapType() {
+ if (mObservableMapType == null) {
+ mObservableMapType = loadClassErasure(OBSERVABLE_MAP_CLASS_NAME);
+ }
+ return mObservableMapType;
+ }
+
+ ModelClass getViewDataBindingType() {
+ if (mViewBindingType == null) {
+ mViewBindingType = findClass(VIEW_DATA_BINDING, null);
+ }
+ return mViewBindingType;
+ }
+
+ ModelClass[] getObservableFieldTypes() {
+ if (mObservableFieldTypes == null) {
+ mObservableFieldTypes = new ModelClass[OBSERVABLE_FIELDS.length];
+ for (int i = 0; i < OBSERVABLE_FIELDS.length; i++) {
+ mObservableFieldTypes[i] = loadClassErasure(OBSERVABLE_FIELDS[i]);
+ }
+ }
+ return mObservableFieldTypes;
+ }
+
+ ModelClass getViewStubType() {
+ if (mViewStubType == null) {
+ mViewStubType = findClass(VIEW_STUB_CLASS_NAME, null);
+ }
+ return mViewStubType;
+ }
+
+ private ModelClass loadClassErasure(String className) {
+ return findClass(className, null).erasure();
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java
new file mode 100644
index 0000000..c55a400
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.tool.reflection;
+
+import android.databinding.tool.util.L;
+
+import org.apache.commons.lang3.StringUtils;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class ModelClass {
+
+ public abstract String toJavaCode();
+
+ /**
+ * @return whether this ModelClass represents an array.
+ */
+ public abstract boolean isArray();
+
+ /**
+ * For arrays, lists, and maps, this returns the contained value. For other types, null
+ * is returned.
+ *
+ * @return The component type for arrays, the value type for maps, and the element type
+ * for lists.
+ */
+ public abstract ModelClass getComponentType();
+
+ /**
+ * @return Whether or not this ModelClass can be treated as a List. This means
+ * it is a java.util.List, or one of the Sparse*Array classes.
+ */
+ public boolean isList() {
+ for (ModelClass listType : ModelAnalyzer.getInstance().getListTypes()) {
+ if (listType != null) {
+ if (listType.isAssignableFrom(this)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return whether or not this ModelClass can be considered a Map or not.
+ */
+ public boolean isMap() {
+ return ModelAnalyzer.getInstance().getMapType().isAssignableFrom(erasure());
+ }
+
+ /**
+ * @return whether or not this ModelClass is a java.lang.String.
+ */
+ public boolean isString() {
+ return ModelAnalyzer.getInstance().getStringType().equals(this);
+ }
+
+ /**
+ * @return whether or not this ModelClass represents a Reference type.
+ */
+ public abstract boolean isNullable();
+
+ /**
+ * @return whether or not this ModelClass represents a primitive type.
+ */
+ public abstract boolean isPrimitive();
+
+ /**
+ * @return whether or not this ModelClass represents a Java boolean
+ */
+ public abstract boolean isBoolean();
+
+ /**
+ * @return whether or not this ModelClass represents a Java char
+ */
+ public abstract boolean isChar();
+
+ /**
+ * @return whether or not this ModelClass represents a Java byte
+ */
+ public abstract boolean isByte();
+
+ /**
+ * @return whether or not this ModelClass represents a Java short
+ */
+ public abstract boolean isShort();
+
+ /**
+ * @return whether or not this ModelClass represents a Java int
+ */
+ public abstract boolean isInt();
+
+ /**
+ * @return whether or not this ModelClass represents a Java long
+ */
+ public abstract boolean isLong();
+
+ /**
+ * @return whether or not this ModelClass represents a Java float
+ */
+ public abstract boolean isFloat();
+
+ /**
+ * @return whether or not this ModelClass represents a Java double
+ */
+ public abstract boolean isDouble();
+
+ /**
+ * @return whether or not this ModelClass is java.lang.Object and not a primitive or subclass.
+ */
+ public boolean isObject() {
+ return ModelAnalyzer.getInstance().getObjectType().equals(this);
+ }
+
+ /**
+ * @return whether or not this ModelClass type extends ViewStub.
+ */
+ public boolean extendsViewStub() {
+ return ModelAnalyzer.getInstance().getViewStubType().isAssignableFrom(this);
+ }
+
+ /**
+ * @return whether or not this is an Observable type such as ObservableMap, ObservableList,
+ * or Observable.
+ */
+ public boolean isObservable() {
+ ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
+ return modelAnalyzer.getObservableType().isAssignableFrom(this) ||
+ modelAnalyzer.getObservableListType().isAssignableFrom(this) ||
+ modelAnalyzer.getObservableMapType().isAssignableFrom(this);
+
+ }
+
+ /**
+ * @return whether or not this is an ObservableField, or any of the primitive versions
+ * such as ObservableBoolean and ObservableInt
+ */
+ public boolean isObservableField() {
+ ModelClass erasure = erasure();
+ for (ModelClass observableField : ModelAnalyzer.getInstance().getObservableFieldTypes()) {
+ if (observableField.isAssignableFrom(erasure)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return whether or not this ModelClass represents a void
+ */
+ public abstract boolean isVoid();
+
+ /**
+ * When this is a boxed type, such as Integer, this will return the unboxed value,
+ * such as int. If this is not a boxed type, this is returned.
+ *
+ * @return The unboxed type of the class that this ModelClass represents or this if it isn't a
+ * boxed type.
+ */
+ public abstract ModelClass unbox();
+
+ /**
+ * When this is a primitive type, such as boolean, this will return the boxed value,
+ * such as Boolean. If this is not a primitive type, this is returned.
+ *
+ * @return The boxed type of the class that this ModelClass represents or this if it isn't a
+ * primitive type.
+ */
+ public abstract ModelClass box();
+
+ /**
+ * Returns whether or not the type associated with <code>that</code> can be assigned to
+ * the type associated with this ModelClass. If this and that only require boxing or unboxing
+ * then true is returned.
+ *
+ * @param that the ModelClass to compare.
+ * @return true if <code>that</code> requires only boxing or if <code>that</code> is an
+ * implementation of or subclass of <code>this</code>.
+ */
+ public abstract boolean isAssignableFrom(ModelClass that);
+
+ /**
+ * Returns an array containing all public methods on the type represented by this ModelClass
+ * with the name <code>name</code> and can take the passed-in types as arguments. This will
+ * also work if the arguments match VarArgs parameter.
+ *
+ * @param name The name of the method to find.
+ * @param args The types that the method should accept.
+ * @param isStatic Whether only static methods should be returned or instance methods.
+ * @return An array containing all public methods with the name <code>name</code> and taking
+ * <code>args</code> parameters.
+ */
+ public ModelMethod[] getMethods(String name, List<ModelClass> args, boolean isStatic) {
+ ModelMethod[] methods = getDeclaredMethods();
+ ArrayList<ModelMethod> matching = new ArrayList<ModelMethod>();
+ for (ModelMethod method : methods) {
+ if (method.isPublic() && method.isStatic() == isStatic &&
+ name.equals(method.getName()) && method.acceptsArguments(args)) {
+ matching.add(method);
+ }
+ }
+ return matching.toArray(new ModelMethod[matching.size()]);
+ }
+
+ /**
+ * Returns all public instance methods with the given name and number of parameters.
+ *
+ * @param name The name of the method to find.
+ * @param numParameters The number of parameters that the method should take
+ * @return An array containing all public methods with the given name and number of parameters.
+ */
+ public ModelMethod[] getMethods(String name, int numParameters) {
+ ModelMethod[] methods = getDeclaredMethods();
+ ArrayList<ModelMethod> matching = new ArrayList<ModelMethod>();
+ for (ModelMethod method : methods) {
+ if (method.isPublic() && !method.isStatic() &&
+ name.equals(method.getName()) &&
+ method.getParameterTypes().length == numParameters) {
+ matching.add(method);
+ }
+ }
+ return matching.toArray(new ModelMethod[matching.size()]);
+ }
+
+ /**
+ * Returns the public method with the name <code>name</code> with the parameters that
+ * best match args. <code>staticAccess</code> governs whether a static or instance method
+ * will be returned. If no matching method was found, null is returned.
+ *
+ * @param name The method name to find
+ * @param args The arguments that the method should accept
+ * @param staticAccess true if the returned method should be static or false if it should
+ * be an instance method.
+ */
+ public ModelMethod getMethod(String name, List<ModelClass> args, boolean staticAccess) {
+ ModelMethod[] methods = getMethods(name, args, staticAccess);
+ if (methods.length == 0) {
+ return null;
+ }
+ ModelMethod bestMethod = methods[0];
+ for (int i = 1; i < methods.length; i++) {
+ if (methods[i].isBetterArgMatchThan(bestMethod, args)) {
+ bestMethod = methods[i];
+ }
+ }
+ return bestMethod;
+ }
+
+ /**
+ * If this represents a class, the super class that it extends is returned. If this
+ * represents an interface, the interface that this extends is returned.
+ * <code>null</code> is returned if this is not a class or interface, such as an int, or
+ * if it is java.lang.Object or an interface that does not extend any other type.
+ *
+ * @return The class or interface that this ModelClass extends or null.
+ */
+ public abstract ModelClass getSuperclass();
+
+ /**
+ * @return A String representation of the class or interface that this represents, not
+ * including any type arguments.
+ */
+ public String getCanonicalName() {
+ return erasure().toJavaCode();
+ }
+
+ /**
+ * Returns this class type without any generic type arguments.
+ * @return this class type without any generic type arguments.
+ */
+ public abstract ModelClass erasure();
+
+ /**
+ * Since when this class is available. Important for Binding expressions so that we don't
+ * call non-existing APIs when setting UI.
+ *
+ * @return The SDK_INT where this method was added. If it is not a framework method, should
+ * return 1.
+ */
+ public int getMinApi() {
+ return SdkUtil.getMinApi(this);
+ }
+
+ /**
+ * Returns the JNI description of the method which can be used to lookup it in SDK.
+ * @see TypeUtil
+ */
+ public abstract String getJniDescription();
+
+ /**
+ * Returns the getter method or field that the name refers to.
+ * @param name The name of the field or the body of the method name -- can be name(),
+ * getName(), or isName().
+ * @param staticAccess Whether this should look for static methods and fields or instance
+ * versions
+ * @return the getter method or field that the name refers to.
+ * @throws IllegalArgumentException if there is no such method or field available.
+ */
+ public Callable findGetterOrField(String name, boolean staticAccess) {
+ String capitalized = StringUtils.capitalize(name);
+ String[] methodNames = {
+ "get" + capitalized,
+ "is" + capitalized,
+ name
+ };
+ final ModelField backingField = getField(name, true, staticAccess);
+ L.d("Finding getter or field for %s, field = %s", name, backingField == null ? null : backingField.getName());
+ for (String methodName : methodNames) {
+ ModelMethod[] methods = getMethods(methodName, 0);
+ for (ModelMethod method : methods) {
+ if (method.isPublic() && method.isStatic() == staticAccess) {
+ final Callable result = new Callable(Callable.Type.METHOD, methodName,
+ method.getReturnType(null), true, method.isBindable() ||
+ (backingField != null && backingField.isBindable()));
+ L.d("backing field for %s is %s", result, backingField);
+ return result;
+ }
+ }
+ }
+
+ if (backingField != null && backingField.isPublic()) {
+ ModelClass fieldType = backingField.getFieldType();
+ return new Callable(Callable.Type.FIELD, name, fieldType,
+ !backingField.isFinal() || fieldType.isObservable(), backingField.isBindable());
+ }
+ throw new IllegalArgumentException(
+ "cannot find " + name + " in " + toJavaCode());
+
+ }
+
+ public ModelField getField(String name, boolean allowPrivate, boolean staticAccess) {
+ ModelField[] fields = getDeclaredFields();
+ for (ModelField field : fields) {
+ if (name.equals(stripFieldName(field.getName())) && field.isStatic() == staticAccess &&
+ (allowPrivate || !field.isPublic())) {
+ return field;
+ }
+ }
+ return null;
+ }
+
+ protected abstract ModelField[] getDeclaredFields();
+
+ protected abstract ModelMethod[] getDeclaredMethods();
+
+ private static String stripFieldName(String fieldName) {
+ // TODO: Make this configurable through IntelliJ
+ if (fieldName.length() > 2) {
+ final char start = fieldName.charAt(2);
+ if (fieldName.startsWith("m_") && Character.isJavaIdentifierStart(start)) {
+ return Character.toLowerCase(start) + fieldName.substring(3);
+ }
+ }
+ if (fieldName.length() > 1) {
+ final char start = fieldName.charAt(1);
+ final char fieldIdentifier = fieldName.charAt(0);
+ final boolean strip;
+ if (fieldIdentifier == '_') {
+ strip = true;
+ } else if (fieldIdentifier == 'm' && Character.isJavaIdentifierStart(start) &&
+ !Character.isLowerCase(start)) {
+ strip = true;
+ } else {
+ strip = false; // not mUppercase format
+ }
+ if (strip) {
+ return Character.toLowerCase(start) + fieldName.substring(2);
+ }
+ }
+ return fieldName;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelField.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelField.java
new file mode 100644
index 0000000..0cde85b
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelField.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.tool.reflection;
+
+public abstract class ModelField {
+
+ /**
+ * @return Whether this field has been annotated with Bindable.
+ */
+ public abstract boolean isBindable();
+
+ /**
+ * @return The field name.
+ */
+ public abstract String getName();
+
+ /**
+ * @return true if this field is marked public.
+ */
+ public abstract boolean isPublic();
+
+ /**
+ * @return true if this is a static field.
+ */
+ public abstract boolean isStatic();
+
+ /**
+ * @return true if the field was declared final.
+ */
+ public abstract boolean isFinal();
+
+ /**
+ * @return The declared type of the field variable.
+ */
+ public abstract ModelClass getFieldType();
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java
new file mode 100644
index 0000000..b4ee671
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.tool.reflection;
+
+import android.databinding.Bindable;
+
+import java.util.List;
+
+public abstract class ModelMethod {
+ public abstract ModelClass getDeclaringClass();
+
+ public abstract ModelClass[] getParameterTypes();
+
+ public abstract String getName();
+
+ public abstract ModelClass getReturnType(List<ModelClass> args);
+
+ public abstract boolean isVoid();
+
+ public abstract boolean isPublic();
+
+ public abstract boolean isStatic();
+
+ /**
+ * @return whether or not this method has been given the {@link Bindable} annotation.
+ */
+ public abstract boolean isBindable();
+
+ /**
+ * Since when this method is available. Important for Binding expressions so that we don't
+ * call non-existing APIs when setting UI.
+ *
+ * @return The SDK_INT where this method was added. If it is not a framework method, should
+ * return 1.
+ */
+ public abstract int getMinApi();
+
+ /**
+ * Returns the JNI description of the method which can be used to lookup it in SDK.
+ * @see TypeUtil
+ */
+ public abstract String getJniDescription();
+
+ /**
+ * @return true if the final parameter is a varargs parameter.
+ */
+ public abstract boolean isVarArgs();
+
+ /**
+ * @param args The arguments to the method
+ * @return Whether the arguments would be accepted as parameters to this method.
+ */
+ public boolean acceptsArguments(List<ModelClass> args) {
+ boolean isVarArgs = isVarArgs();
+ ModelClass[] parameterTypes = getParameterTypes();
+ if ((!isVarArgs && args.size() != parameterTypes.length) ||
+ (isVarArgs && args.size() < parameterTypes.length - 1)) {
+ return false; // The wrong number of parameters
+ }
+ boolean parametersMatch = true;
+ for (int i = 0; i < args.size(); i++) {
+ ModelClass parameterType = getParameter(i, parameterTypes);
+ ModelClass arg = args.get(i);
+ if (!parameterType.isAssignableFrom(arg) && !isImplicitConversion(arg, parameterType)) {
+ parametersMatch = false;
+ break;
+ }
+ }
+ return parametersMatch;
+ }
+
+ public boolean isBetterArgMatchThan(ModelMethod other, List<ModelClass> args) {
+ final ModelClass[] parameterTypes = getParameterTypes();
+ final ModelClass[] otherParameterTypes = other.getParameterTypes();
+ for (int i = 0; i < args.size(); i++) {
+ final ModelClass arg = args.get(i);
+ final ModelClass thisParameter = getParameter(i, parameterTypes);
+ final ModelClass thatParameter = other.getParameter(i, otherParameterTypes);
+ final int diff = compareParameter(arg, thisParameter, thatParameter);
+ if (diff != 0) {
+ return diff < 0;
+ }
+ }
+ return false;
+ }
+
+ private ModelClass getParameter(int index, ModelClass[] parameterTypes) {
+ int normalParamCount = isVarArgs() ? parameterTypes.length - 1 : parameterTypes.length;
+ if (index < normalParamCount) {
+ return parameterTypes[index];
+ } else {
+ return parameterTypes[parameterTypes.length - 1].getComponentType();
+ }
+ }
+
+ private static int compareParameter(ModelClass arg, ModelClass thisParameter,
+ ModelClass thatParameter) {
+ if (thatParameter.equals(arg)) {
+ return 1;
+ } else if (thisParameter.equals(arg)) {
+ return -1;
+ } else if (isBoxingConversion(thatParameter, arg)) {
+ return 1;
+ } else if (isBoxingConversion(thisParameter, arg)) {
+ // Boxing/unboxing is second best
+ return -1;
+ } else {
+ int argConversionLevel = getImplicitConversionLevel(arg);
+ if (argConversionLevel != -1) {
+ int oldConversionLevel = getImplicitConversionLevel(thatParameter);
+ int newConversionLevel = getImplicitConversionLevel(thisParameter);
+ if (newConversionLevel != -1 &&
+ (oldConversionLevel == -1 || newConversionLevel < oldConversionLevel)) {
+ return -1;
+ } else if (oldConversionLevel != -1) {
+ return 1;
+ }
+ }
+ // Look for more exact match
+ if (thatParameter.isAssignableFrom(thisParameter)) {
+ return -1;
+ }
+ }
+ return 0; // no difference
+ }
+
+ public static boolean isBoxingConversion(ModelClass class1, ModelClass class2) {
+ if (class1.isPrimitive() != class2.isPrimitive()) {
+ return (class1.box().equals(class2.box()));
+ } else {
+ return false;
+ }
+ }
+
+ public static int getImplicitConversionLevel(ModelClass primitive) {
+ if (primitive == null) {
+ return -1;
+ } else if (primitive.isByte()) {
+ return 0;
+ } else if (primitive.isChar()) {
+ return 1;
+ } else if (primitive.isShort()) {
+ return 2;
+ } else if (primitive.isInt()) {
+ return 3;
+ } else if (primitive.isLong()) {
+ return 4;
+ } else if (primitive.isFloat()) {
+ return 5;
+ } else if (primitive.isDouble()) {
+ return 6;
+ } else {
+ return -1;
+ }
+ }
+
+ public static boolean isImplicitConversion(ModelClass from, ModelClass to) {
+ if (from != null && to != null && from.isPrimitive() && to.isPrimitive()) {
+ if (from.isBoolean() || to.isBoolean() || to.isChar()) {
+ return false;
+ }
+ int fromConversionLevel = getImplicitConversionLevel(from);
+ int toConversionLevel = getImplicitConversionLevel(to);
+ return fromConversionLevel < toConversionLevel;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/SdkUtil.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/SdkUtil.java
new file mode 100644
index 0000000..177935a
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/SdkUtil.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 android.databinding.tool.reflection;
+
+import com.google.common.base.Preconditions;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import android.databinding.tool.util.L;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+/**
+ * Class that is used for SDK related stuff.
+ * <p>
+ * Must be initialized with the sdk location to work properly
+ */
+public class SdkUtil {
+
+ static File mSdkPath;
+
+ static ApiChecker mApiChecker;
+
+ static int mMinSdk;
+
+ public static void initialize(int minSdk, File sdkPath) {
+ mSdkPath = sdkPath;
+ mMinSdk = minSdk;
+ mApiChecker = new ApiChecker(new File(sdkPath.getAbsolutePath()
+ + "/platform-tools/api/api-versions.xml"));
+ L.d("SdkUtil init, minSdk: %s", minSdk);
+ }
+
+ public static int getMinApi(ModelClass modelClass) {
+ return mApiChecker.getMinApi(modelClass.getJniDescription(), null);
+ }
+
+ public static int getMinApi(ModelMethod modelMethod) {
+ ModelClass declaringClass = modelMethod.getDeclaringClass();
+ Preconditions.checkNotNull(mApiChecker, "should've initialized api checker");
+ while (declaringClass != null) {
+ String classDesc = declaringClass.getJniDescription();
+ String methodDesc = modelMethod.getJniDescription();
+ int result = mApiChecker.getMinApi(classDesc, methodDesc);
+ L.d("checking method api for %s, class:%s method:%s. result: %d", modelMethod.getName(),
+ classDesc, methodDesc, result);
+ if (result > 1) {
+ return result;
+ }
+ declaringClass = declaringClass.getSuperclass();
+ }
+ return 1;
+ }
+
+ private static class ApiChecker {
+
+ private Map<String, Integer> mFullLookup = new HashMap<String, Integer>();
+
+ private Document mDoc;
+
+ private XPath mXPath;
+
+ public ApiChecker(File apiFile) {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ mDoc = builder.parse(apiFile);
+ XPathFactory xPathFactory = XPathFactory.newInstance();
+ mXPath = xPathFactory.newXPath();
+ buildFullLookup();
+ } catch (Throwable t) {
+ L.e(t, "cannot load api descriptions from %s", apiFile);
+ }
+ }
+
+ private void buildFullLookup() throws XPathExpressionException {
+ NodeList allClasses = mDoc.getChildNodes().item(0).getChildNodes();
+ for (int j = 0; j < allClasses.getLength(); j++) {
+ Node node = allClasses.item(j);
+ if (node.getNodeType() != Node.ELEMENT_NODE || !"class"
+ .equals(node.getNodeName())) {
+ continue;
+ }
+ //L.d("checking node %s", node.getAttributes().getNamedItem("name").getNodeValue());
+ int classSince = getSince(node);
+ String classDesc = node.getAttributes().getNamedItem("name").getNodeValue();
+
+ final NodeList childNodes = node.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node child = childNodes.item(i);
+ if (child.getNodeType() != Node.ELEMENT_NODE || !"method"
+ .equals(child.getNodeName())) {
+ continue;
+ }
+ int methodSince = getSince(child);
+ int since = Math.max(classSince, methodSince);
+ if (since > SdkUtil.mMinSdk) {
+ String methodDesc = child.getAttributes().getNamedItem("name")
+ .getNodeValue();
+ String key = cacheKey(classDesc, methodDesc);
+ mFullLookup.put(key, since);
+ }
+ }
+ }
+ }
+
+ public int getMinApi(String classDesc, String methodOrFieldDesc) {
+ if (mDoc == null || mXPath == null) {
+ return 1;
+ }
+ if (classDesc == null || classDesc.isEmpty()) {
+ return 1;
+ }
+ final String key = cacheKey(classDesc, methodOrFieldDesc);
+ Integer since = mFullLookup.get(key);
+ return since == null ? 1 : since;
+ }
+
+ private static String cacheKey(String classDesc, String methodOrFieldDesc) {
+ return classDesc + "~" + methodOrFieldDesc;
+ }
+
+ private static int getSince(Node node) {
+ final Node since = node.getAttributes().getNamedItem("since");
+ if (since != null) {
+ final String nodeValue = since.getNodeValue();
+ if (nodeValue != null && !nodeValue.isEmpty()) {
+ try {
+ return Integer.parseInt(nodeValue);
+ } catch (Throwable t) {
+ }
+ }
+ }
+
+ return 1;
+ }
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/TypeUtil.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/TypeUtil.java
new file mode 100644
index 0000000..f396bd7
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/TypeUtil.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.reflection;
+
+public abstract class TypeUtil {
+
+ public static final String BYTE = "B";
+
+ public static final String CHAR = "C";
+
+ public static final String DOUBLE = "D";
+
+ public static final String FLOAT = "F";
+
+ public static final String INT = "I";
+
+ public static final String LONG = "J";
+
+ public static final String SHORT = "S";
+
+ public static final String VOID = "V";
+
+ public static final String BOOLEAN = "Z";
+
+ public static final String ARRAY = "[";
+
+ public static final String CLASS_PREFIX = "L";
+
+ public static final String CLASS_SUFFIX = ";";
+
+ private static TypeUtil sInstance;
+
+ abstract public String getDescription(ModelClass modelClass);
+
+ abstract public String getDescription(ModelMethod modelMethod);
+
+ public static TypeUtil getInstance() {
+ if (sInstance == null) {
+ sInstance = ModelAnalyzer.getInstance().createTypeUtil();
+ }
+ return sInstance;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationAnalyzer.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationAnalyzer.java
new file mode 100644
index 0000000..d01f579
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationAnalyzer.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.tool.reflection.annotation;
+
+import com.google.common.collect.ImmutableMap;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.TypeUtil;
+import android.databinding.tool.util.L;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+public class AnnotationAnalyzer extends ModelAnalyzer {
+
+ public static final Map<String, TypeKind> PRIMITIVE_TYPES =
+ new ImmutableMap.Builder<String, TypeKind>()
+ .put("boolean", TypeKind.BOOLEAN)
+ .put("byte", TypeKind.BYTE)
+ .put("short", TypeKind.SHORT)
+ .put("char", TypeKind.CHAR)
+ .put("int", TypeKind.INT)
+ .put("long", TypeKind.LONG)
+ .put("float", TypeKind.FLOAT)
+ .put("double", TypeKind.DOUBLE)
+ .build();
+
+ public final ProcessingEnvironment mProcessingEnv;
+
+ public AnnotationAnalyzer(ProcessingEnvironment processingEnvironment) {
+ mProcessingEnv = processingEnvironment;
+ setInstance(this);
+ }
+
+ public static AnnotationAnalyzer get() {
+ return (AnnotationAnalyzer) getInstance();
+ }
+
+ @Override
+ public AnnotationClass loadPrimitive(String className) {
+ TypeKind typeKind = PRIMITIVE_TYPES.get(className);
+ if (typeKind == null) {
+ return null;
+ } else {
+ Types typeUtils = getTypeUtils();
+ return new AnnotationClass(typeUtils.getPrimitiveType(typeKind));
+ }
+ }
+
+ @Override
+ public AnnotationClass findClass(String className, Map<String, String> imports) {
+ className = className.trim();
+ int numDimensions = 0;
+ while (className.endsWith("[]")) {
+ numDimensions++;
+ className = className.substring(0, className.length() - 2);
+ }
+ AnnotationClass primitive = loadPrimitive(className);
+ if (primitive != null) {
+ return addDimension(primitive.mTypeMirror, numDimensions);
+ }
+ int templateOpenIndex = className.indexOf('<');
+ DeclaredType declaredType;
+ if (templateOpenIndex < 0) {
+ TypeElement typeElement = getTypeElement(className, imports);
+ if (typeElement == null) {
+ return null;
+ }
+ declaredType = (DeclaredType) typeElement.asType();
+ } else {
+ int templateCloseIndex = className.lastIndexOf('>');
+ String paramStr = className.substring(templateOpenIndex + 1, templateCloseIndex);
+
+ String baseClassName = className.substring(0, templateOpenIndex);
+ TypeElement typeElement = getTypeElement(baseClassName, imports);
+ if (typeElement == null) {
+ L.e("cannot find type element for %s", baseClassName);
+ return null;
+ }
+
+ ArrayList<String> templateParameters = splitTemplateParameters(paramStr);
+ TypeMirror[] typeArgs = new TypeMirror[templateParameters.size()];
+ for (int i = 0; i < typeArgs.length; i++) {
+ typeArgs[i] = findClass(templateParameters.get(i), imports).mTypeMirror;
+ if (typeArgs[i] == null) {
+ L.e("cannot find type argument for %s in %s", templateParameters.get(i),
+ baseClassName);
+ return null;
+ }
+ }
+ Types typeUtils = getTypeUtils();
+ declaredType = typeUtils.getDeclaredType(typeElement, typeArgs);
+ }
+ return addDimension(declaredType, numDimensions);
+ }
+
+ private AnnotationClass addDimension(TypeMirror type, int numDimensions) {
+ while (numDimensions > 0) {
+ type = getTypeUtils().getArrayType(type);
+ numDimensions--;
+ }
+ return new AnnotationClass(type);
+ }
+
+ private TypeElement getTypeElement(String className, Map<String, String> imports) {
+ Elements elementUtils = getElementUtils();
+ if (className.indexOf('.') < 0 && imports != null) {
+ // try the imports
+ String importedClass = imports.get(className);
+ if (importedClass != null) {
+ className = importedClass;
+ }
+ }
+ if (className.indexOf('.') < 0) {
+ // try java.lang.
+ String javaLangClass = "java.lang." + className;
+ try {
+ TypeElement javaLang = elementUtils.getTypeElement(javaLangClass);
+ if (javaLang != null) {
+ return javaLang;
+ }
+ } catch (Exception e) {
+ // try the normal way
+ }
+ }
+ try {
+ return elementUtils.getTypeElement(className);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private ArrayList<String> splitTemplateParameters(String templateParameters) {
+ ArrayList<String> list = new ArrayList<String>();
+ int index = 0;
+ int openCount = 0;
+ StringBuilder arg = new StringBuilder();
+ while (index < templateParameters.length()) {
+ char c = templateParameters.charAt(index);
+ if (c == ',' && openCount == 0) {
+ list.add(arg.toString());
+ arg.delete(0, arg.length());
+ } else if (!Character.isWhitespace(c)) {
+ arg.append(c);
+ if (c == '<') {
+ openCount++;
+ } else if (c == '>') {
+ openCount--;
+ }
+ }
+ index++;
+ }
+ list.add(arg.toString());
+ return list;
+ }
+
+ @Override
+ public ModelClass findClass(Class classType) {
+ return findClass(classType.getCanonicalName(), null);
+ }
+
+ public Types getTypeUtils() {
+ return mProcessingEnv.getTypeUtils();
+ }
+
+ public Elements getElementUtils() {
+ return mProcessingEnv.getElementUtils();
+ }
+
+ public ProcessingEnvironment getProcessingEnv() {
+ return mProcessingEnv;
+ }
+
+ @Override
+ public TypeUtil createTypeUtil() {
+ return new AnnotationTypeUtil(this);
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationClass.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationClass.java
new file mode 100644
index 0000000..a1bec3c
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationClass.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.tool.reflection.annotation;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.ModelField;
+import android.databinding.tool.reflection.ModelMethod;
+import android.databinding.tool.reflection.TypeUtil;
+import android.databinding.tool.util.L;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+/**
+ * This is the implementation of ModelClass for the annotation
+ * processor. It relies on AnnotationAnalyzer.
+ */
+class AnnotationClass extends ModelClass {
+
+ final TypeMirror mTypeMirror;
+
+ public AnnotationClass(TypeMirror typeMirror) {
+ mTypeMirror = typeMirror;
+ }
+
+ @Override
+ public String toJavaCode() {
+ return mTypeMirror.toString();
+ }
+
+ @Override
+ public boolean isArray() {
+ return mTypeMirror.getKind() == TypeKind.ARRAY;
+ }
+
+ @Override
+ public AnnotationClass getComponentType() {
+ TypeMirror component = null;
+ if (isArray()) {
+ component = ((ArrayType) mTypeMirror).getComponentType();
+ } else if (isList()) {
+ for (ModelMethod method : getMethods("get", 1)) {
+ ModelClass parameter = method.getParameterTypes()[0];
+ if (parameter.isInt() || parameter.isLong()) {
+ ArrayList<ModelClass> parameters = new ArrayList<ModelClass>(1);
+ parameters.add(parameter);
+ return (AnnotationClass) method.getReturnType(parameters);
+ }
+ }
+ // no "get" call found!
+ return null;
+ } else {
+ AnnotationClass mapClass = (AnnotationClass) ModelAnalyzer.getInstance().getMapType();
+ DeclaredType mapType = findInterface(mapClass.mTypeMirror);
+ if (mapType == null) {
+ return null;
+ }
+ component = mapType.getTypeArguments().get(1);
+ }
+
+ return new AnnotationClass(component);
+ }
+
+ private DeclaredType findInterface(TypeMirror interfaceType) {
+ Types typeUtil = getTypeUtils();
+ TypeMirror foundInterface = null;
+ if (typeUtil.isSameType(interfaceType, typeUtil.erasure(mTypeMirror))) {
+ foundInterface = mTypeMirror;
+ } else {
+ ArrayList<TypeMirror> toCheck = new ArrayList<TypeMirror>();
+ toCheck.add(mTypeMirror);
+ while (!toCheck.isEmpty()) {
+ TypeMirror typeMirror = toCheck.remove(0);
+ if (typeUtil.isSameType(interfaceType, typeUtil.erasure(typeMirror))) {
+ foundInterface = typeMirror;
+ break;
+ } else {
+ toCheck.addAll(typeUtil.directSupertypes(typeMirror));
+ }
+ }
+ if (foundInterface == null) {
+ L.e("Detected " + interfaceType + " type for " + mTypeMirror +
+ ", but not able to find the implemented interface.");
+ return null;
+ }
+ }
+ if (foundInterface.getKind() != TypeKind.DECLARED) {
+ L.e("Found " + interfaceType + " type for " + mTypeMirror +
+ ", but it isn't a declared type: " + foundInterface);
+ return null;
+ }
+ return (DeclaredType) foundInterface;
+ }
+
+ @Override
+ public boolean isNullable() {
+ switch (mTypeMirror.getKind()) {
+ case ARRAY:
+ case DECLARED:
+ case NULL:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ switch (mTypeMirror.getKind()) {
+ case BOOLEAN:
+ case BYTE:
+ case SHORT:
+ case INT:
+ case LONG:
+ case CHAR:
+ case FLOAT:
+ case DOUBLE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isBoolean() {
+ return mTypeMirror.getKind() == TypeKind.BOOLEAN;
+ }
+
+ @Override
+ public boolean isChar() {
+ return mTypeMirror.getKind() == TypeKind.CHAR;
+ }
+
+ @Override
+ public boolean isByte() {
+ return mTypeMirror.getKind() == TypeKind.BYTE;
+ }
+
+ @Override
+ public boolean isShort() {
+ return mTypeMirror.getKind() == TypeKind.SHORT;
+ }
+
+ @Override
+ public boolean isInt() {
+ return mTypeMirror.getKind() == TypeKind.INT;
+ }
+
+ @Override
+ public boolean isLong() {
+ return mTypeMirror.getKind() == TypeKind.LONG;
+ }
+
+ @Override
+ public boolean isFloat() {
+ return mTypeMirror.getKind() == TypeKind.FLOAT;
+ }
+
+ @Override
+ public boolean isDouble() {
+ return mTypeMirror.getKind() == TypeKind.DOUBLE;
+ }
+
+ @Override
+ public boolean isVoid() {
+ return mTypeMirror.getKind() == TypeKind.VOID;
+ }
+
+ @Override
+ public AnnotationClass unbox() {
+ if (!isNullable()) {
+ return this;
+ }
+ try {
+ return new AnnotationClass(getTypeUtils().unboxedType(mTypeMirror));
+ } catch (IllegalArgumentException e) {
+ // I'm being lazy. This is much easier than checking every type.
+ return this;
+ }
+ }
+
+ @Override
+ public AnnotationClass box() {
+ if (!isPrimitive()) {
+ return this;
+ }
+ return new AnnotationClass(getTypeUtils().boxedClass((PrimitiveType) mTypeMirror).asType());
+ }
+
+ @Override
+ public boolean isAssignableFrom(ModelClass that) {
+ if (that == null) {
+ return false;
+ }
+ AnnotationClass thatAnnotationClass = (AnnotationClass) that;
+ return getTypeUtils().isAssignable(thatAnnotationClass.mTypeMirror, this.mTypeMirror);
+ }
+
+ @Override
+ public ModelMethod[] getDeclaredMethods() {
+ final ModelMethod[] declaredMethods;
+ if (mTypeMirror.getKind() == TypeKind.DECLARED) {
+ DeclaredType declaredType = (DeclaredType) mTypeMirror;
+ Elements elementUtils = getElementUtils();
+ TypeElement typeElement = (TypeElement) declaredType.asElement();
+ List<? extends Element> members = elementUtils.getAllMembers(typeElement);
+ List<ExecutableElement> methods = ElementFilter.methodsIn(members);
+ declaredMethods = new ModelMethod[methods.size()];
+ for (int i = 0; i < declaredMethods.length; i++) {
+ declaredMethods[i] = new AnnotationMethod(declaredType, methods.get(i));
+ }
+ } else {
+ declaredMethods = new ModelMethod[0];
+ }
+ return declaredMethods;
+ }
+
+ @Override
+ public AnnotationClass getSuperclass() {
+ if (mTypeMirror.getKind() == TypeKind.DECLARED) {
+ DeclaredType declaredType = (DeclaredType) mTypeMirror;
+ TypeElement typeElement = (TypeElement) declaredType.asElement();
+ TypeMirror superClass = typeElement.getSuperclass();
+ if (superClass.getKind() == TypeKind.DECLARED) {
+ return new AnnotationClass(superClass);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getCanonicalName() {
+ return getTypeUtils().erasure(mTypeMirror).toString();
+ }
+
+ @Override
+ public ModelClass erasure() {
+ final TypeMirror erasure = getTypeUtils().erasure(mTypeMirror);
+ if (erasure == mTypeMirror) {
+ return this;
+ } else {
+ return new AnnotationClass(erasure);
+ }
+ }
+
+ @Override
+ public String getJniDescription() {
+ return TypeUtil.getInstance().getDescription(this);
+ }
+
+ @Override
+ protected ModelField[] getDeclaredFields() {
+ final ModelField[] declaredFields;
+ if (mTypeMirror.getKind() == TypeKind.DECLARED) {
+ DeclaredType declaredType = (DeclaredType) mTypeMirror;
+ Elements elementUtils = getElementUtils();
+ TypeElement typeElement = (TypeElement) declaredType.asElement();
+ List<? extends Element> members = elementUtils.getAllMembers(typeElement);
+ List<VariableElement> fields = ElementFilter.fieldsIn(members);
+ declaredFields = new ModelField[fields.size()];
+ for (int i = 0; i < declaredFields.length; i++) {
+ declaredFields[i] = new AnnotationField(typeElement, fields.get(i));
+ }
+ } else {
+ declaredFields = new ModelField[0];
+ }
+ return declaredFields;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof AnnotationClass) {
+ return getTypeUtils().isSameType(mTypeMirror, ((AnnotationClass) obj).mTypeMirror);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return mTypeMirror.toString().hashCode();
+ }
+
+ private static Types getTypeUtils() {
+ return AnnotationAnalyzer.get().mProcessingEnv.getTypeUtils();
+ }
+
+ private static Elements getElementUtils() {
+ return AnnotationAnalyzer.get().mProcessingEnv.getElementUtils();
+ }
+
+ @Override
+ public String toString() {
+ return mTypeMirror.toString();
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationField.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationField.java
new file mode 100644
index 0000000..9373c6c
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationField.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.tool.reflection.annotation;
+
+import android.databinding.Bindable;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.ModelField;
+
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+
+class AnnotationField extends ModelField {
+
+ final VariableElement mField;
+
+ final TypeElement mDeclaredClass;
+
+ public AnnotationField(TypeElement declaredClass, VariableElement field) {
+ mDeclaredClass = declaredClass;
+ mField = field;
+ }
+
+ @Override
+ public String toString() {
+ return mField.toString();
+ }
+
+ @Override
+ public boolean isBindable() {
+ return mField.getAnnotation(Bindable.class) != null;
+ }
+
+ @Override
+ public String getName() {
+ return mField.getSimpleName().toString();
+ }
+
+ @Override
+ public boolean isPublic() {
+ return mField.getModifiers().contains(Modifier.PUBLIC);
+ }
+
+ @Override
+ public boolean isStatic() {
+ return mField.getModifiers().contains(Modifier.STATIC);
+ }
+
+ @Override
+ public boolean isFinal() {
+ return mField.getModifiers().contains(Modifier.FINAL);
+ }
+
+ @Override
+ public ModelClass getFieldType() {
+ return new AnnotationClass(mField.asType());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof AnnotationField) {
+ AnnotationField that = (AnnotationField) obj;
+ return mDeclaredClass.equals(that.mDeclaredClass) && AnnotationAnalyzer.get()
+ .getTypeUtils().isSameType(mField.asType(), that.mField.asType());
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationMethod.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationMethod.java
new file mode 100644
index 0000000..00e1a4e
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationMethod.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.tool.reflection.annotation;
+
+import android.databinding.Bindable;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.ModelMethod;
+import android.databinding.tool.reflection.SdkUtil;
+import android.databinding.tool.reflection.TypeUtil;
+
+import java.util.List;
+
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Types;
+
+class AnnotationMethod extends ModelMethod {
+ final ExecutableType mMethod;
+ final DeclaredType mDeclaringType;
+ final ExecutableElement mExecutableElement;
+ int mApiLevel = -1; // calculated on demand
+
+ public AnnotationMethod(DeclaredType declaringType, ExecutableElement executableElement) {
+ mDeclaringType = declaringType;
+ mExecutableElement = executableElement;
+ Types typeUtils = AnnotationAnalyzer.get().getTypeUtils();
+ mMethod = (ExecutableType) typeUtils.asMemberOf(declaringType, executableElement);
+ }
+
+ @Override
+ public ModelClass getDeclaringClass() {
+ return new AnnotationClass(mDeclaringType);
+ }
+
+ @Override
+ public ModelClass[] getParameterTypes() {
+ List<? extends TypeMirror> parameters = mMethod.getParameterTypes();
+ ModelClass[] parameterTypes = new ModelClass[parameters.size()];
+ for (int i = 0; i < parameters.size(); i++) {
+ parameterTypes[i] = new AnnotationClass(parameters.get(i));
+ }
+ return parameterTypes;
+ }
+
+ @Override
+ public String getName() {
+ return mExecutableElement.getSimpleName().toString();
+ }
+
+ @Override
+ public ModelClass getReturnType(List<ModelClass> args) {
+ TypeMirror returnType = mMethod.getReturnType();
+ // TODO: support argument-supplied types
+ // for example: public T[] toArray(T[] arr)
+ return new AnnotationClass(returnType);
+ }
+
+ @Override
+ public boolean isVoid() {
+ return mMethod.getReturnType().getKind() == TypeKind.VOID;
+ }
+
+ @Override
+ public boolean isPublic() {
+ return mExecutableElement.getModifiers().contains(Modifier.PUBLIC);
+ }
+
+ @Override
+ public boolean isStatic() {
+ return mExecutableElement.getModifiers().contains(Modifier.STATIC);
+ }
+
+ @Override
+ public boolean isBindable() {
+ return mExecutableElement.getAnnotation(Bindable.class) != null;
+ }
+
+ @Override
+ public int getMinApi() {
+ if (mApiLevel == -1) {
+ mApiLevel = SdkUtil.getMinApi(this);
+ }
+ return mApiLevel;
+ }
+
+ @Override
+ public String getJniDescription() {
+ return TypeUtil.getInstance().getDescription(this);
+ }
+
+ @Override
+ public boolean isVarArgs() {
+ return mExecutableElement.isVarArgs();
+ }
+
+ @Override
+ public String toString() {
+ return "AnnotationMethod{" +
+ "mMethod=" + mMethod +
+ ", mDeclaringType=" + mDeclaringType +
+ ", mExecutableElement=" + mExecutableElement +
+ ", mApiLevel=" + mApiLevel +
+ '}';
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationTypeUtil.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationTypeUtil.java
new file mode 100644
index 0000000..ebc6a07
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationTypeUtil.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.reflection.annotation;
+
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.ModelMethod;
+import android.databinding.tool.reflection.TypeUtil;
+
+import java.util.List;
+
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+
+public class AnnotationTypeUtil extends TypeUtil {
+ javax.lang.model.util.Types mTypes;
+
+ public AnnotationTypeUtil(
+ AnnotationAnalyzer annotationAnalyzer) {
+ mTypes = annotationAnalyzer.getTypeUtils();
+ }
+
+ @Override
+ public String getDescription(ModelClass modelClass) {
+ // TODO use interface
+ return modelClass.getCanonicalName().replace('.', '/');
+ }
+
+ @Override
+ public String getDescription(ModelMethod modelMethod) {
+ // TODO use interface
+ return modelMethod.getName() + getDescription(
+ ((AnnotationMethod) modelMethod).mExecutableElement.asType());
+ }
+
+ private String getDescription(TypeMirror typeMirror) {
+ if (typeMirror == null) {
+ throw new UnsupportedOperationException();
+ }
+ switch (typeMirror.getKind()) {
+ case BOOLEAN:
+ return BOOLEAN;
+ case BYTE:
+ return BYTE;
+ case SHORT:
+ return SHORT;
+ case INT:
+ return INT;
+ case LONG:
+ return LONG;
+ case CHAR:
+ return CHAR;
+ case FLOAT:
+ return FLOAT;
+ case DOUBLE:
+ return DOUBLE;
+ case DECLARED:
+ return CLASS_PREFIX + mTypes.erasure(typeMirror).toString().replace('.', '/') + CLASS_SUFFIX;
+ case VOID:
+ return VOID;
+ case ARRAY:
+ final ArrayType arrayType = (ArrayType) typeMirror;
+ final String componentType = getDescription(arrayType.getComponentType());
+ return ARRAY + componentType;
+ case TYPEVAR:
+ final TypeVariable typeVariable = (TypeVariable) typeMirror;
+ final String name = typeVariable.toString();
+ return CLASS_PREFIX + name.replace('.', '/') + CLASS_SUFFIX;
+ case EXECUTABLE:
+ final ExecutableType executableType = (ExecutableType) typeMirror;
+ final int argStart = mTypes.erasure(executableType).toString().indexOf('(');
+ final String methodName = executableType.toString().substring(0, argStart);
+ final String args = joinArgs(executableType.getParameterTypes());
+ // TODO detect constructor?
+ return methodName + "(" + args + ")" + getDescription(
+ executableType.getReturnType());
+ default:
+ throw new UnsupportedOperationException("cannot understand type "
+ + typeMirror.toString() + ", kind:" + typeMirror.getKind().name());
+ }
+ }
+
+ private String joinArgs(List<? extends TypeMirror> mirrorList) {
+ StringBuilder result = new StringBuilder();
+ for (TypeMirror mirror : mirrorList) {
+ result.append(getDescription(mirror));
+ }
+ return result.toString();
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/LayoutFileParser.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/LayoutFileParser.java
new file mode 100644
index 0000000..5909922
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/LayoutFileParser.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.store;
+
+import com.google.common.base.Preconditions;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import android.databinding.tool.util.L;
+import android.databinding.tool.util.ParserHelper;
+import android.databinding.tool.util.XmlEditor;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+/**
+ * Gets the list of XML files and creates a list of
+ * {@link android.databinding.tool.store.ResourceBundle} that can be persistent or converted to
+ * LayoutBinder.
+ */
+public class LayoutFileParser {
+ private static final String XPATH_VARIABLE_DEFINITIONS = "//variable";
+ private static final String XPATH_BINDING_ELEMENTS = "//*[@*[starts-with(., '@{') and substring(., string-length(.)) = '}']]";
+ private static final String XPATH_ID_ELEMENTS = "//*[@*[local-name()='id']]";
+ private static final String XPATH_IMPORT_DEFINITIONS = "//import";
+ private static final String XPATH_MERGE_TAG = "/merge";
+ final String LAYOUT_PREFIX = "@layout/";
+
+ public ResourceBundle.LayoutFileBundle parseXml(File xml, String pkg)
+ throws ParserConfigurationException, IOException, SAXException,
+ XPathExpressionException {
+ final String xmlNoExtension = ParserHelper.INSTANCE$.stripExtension(xml.getName());
+ final String newTag = xml.getParentFile().getName() + '/' + xmlNoExtension;
+ File original = stripFileAndGetOriginal(xml, newTag);
+ if (original == null) {
+ L.d("assuming the file is the original for %s", xml.getAbsoluteFile());
+ original = xml;
+ }
+ L.d("parsing file %s", xml.getAbsolutePath());
+
+ ResourceBundle.LayoutFileBundle bundle = new ResourceBundle.LayoutFileBundle(
+ xmlNoExtension, xml.getParentFile().getName(), pkg);
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ final DocumentBuilder builder = factory.newDocumentBuilder();
+ final Document doc = builder.parse(original);
+
+ final XPathFactory xPathFactory = XPathFactory.newInstance();
+ final XPath xPath = xPathFactory.newXPath();
+
+ List<Node> variableNodes = getVariableNodes(doc, xPath);
+
+ L.d("number of variable nodes %d", variableNodes.size());
+ for (Node item : variableNodes) {
+ L.d("reading variable node %s", item);
+ NamedNodeMap attributes = item.getAttributes();
+ String variableName = attributes.getNamedItem("name").getNodeValue();
+ String variableType = attributes.getNamedItem("type").getNodeValue();
+ L.d("name: %s, type:%s", variableName, variableType);
+ bundle.addVariable(variableName, variableType);
+ }
+
+ final List<Node> imports = getImportNodes(doc, xPath);
+ L.d("import node count %d", imports.size());
+ for (Node item : imports) {
+ NamedNodeMap attributes = item.getAttributes();
+ String type = attributes.getNamedItem("type").getNodeValue();
+ final Node aliasNode = attributes.getNamedItem("alias");
+ final String alias;
+ if (aliasNode == null) {
+ final String[] split = StringUtils.split(type, '.');
+ alias = split[split.length - 1];
+ } else {
+ alias = aliasNode.getNodeValue();
+ }
+ bundle.addImport(alias, type);
+ }
+
+ final List<Node> bindingNodes = getBindingNodes(doc, xPath);
+ L.d("number of binding nodes %d", bindingNodes.size());
+ int tagNumber = 0;
+ for (Node parent : bindingNodes) {
+ NamedNodeMap attributes = parent.getAttributes();
+ String nodeName = parent.getNodeName();
+ String className;
+ String includedLayoutName = null;
+ final Node id = attributes.getNamedItem("android:id");
+ if ("include".equals(nodeName)) {
+ if (id == null) {
+ L.e("<include> must have android:id attribute with binding expressions.");
+ throw new RuntimeException("<include> must have android:id attribute " +
+ "with binding expressions.");
+ }
+ // get the layout attribute
+ final Node includedLayout = attributes.getNamedItem("layout");
+ Preconditions.checkNotNull(includedLayout, "must include a layout");
+ final String includeValue = includedLayout.getNodeValue();
+ Preconditions.checkArgument(includeValue.startsWith(LAYOUT_PREFIX));
+ // if user is binding something there, there MUST be a layout file to be
+ // generated.
+ String layoutName = includeValue.substring(LAYOUT_PREFIX.length());
+ className = pkg + ".databinding." +
+ ParserHelper.INSTANCE$.toClassName(layoutName) + "Binding";
+ includedLayoutName = layoutName;
+ } else {
+ className = getFullViewClassName(parent);
+ }
+ final Node originalTag = attributes.getNamedItem("android:tag");
+ final String tag;
+ if (doc.getDocumentElement() == parent) {
+ tag = null;
+ } else {
+ tag = String.valueOf(tagNumber++);
+ }
+ final ResourceBundle.BindingTargetBundle bindingTargetBundle =
+ bundle.createBindingTarget(id == null ? null : id.getNodeValue(),
+ className, true, tag, originalTag == null ? null : originalTag.getNodeValue());
+ bindingTargetBundle.setIncludedLayout(includedLayoutName);
+
+ final int attrCount = attributes.getLength();
+ for (int i = 0; i < attrCount; i ++) {
+ final Node attr = attributes.item(i);
+ String value = attr.getNodeValue();
+ if (value.charAt(0) == '@' && value.charAt(1) == '{' &&
+ value.charAt(value.length() - 1) == '}') {
+ final String strippedValue = value.substring(2, value.length() - 1);
+ bindingTargetBundle.addBinding(attr.getNodeName(), strippedValue);
+ }
+ }
+ }
+
+ if (!bindingNodes.isEmpty() || !imports.isEmpty() || !variableNodes.isEmpty()) {
+ if (isMergeLayout(doc, xPath)) {
+ L.e("<merge> is not allowed with data binding.");
+ throw new RuntimeException("<merge> is not allowed with data binding.");
+ }
+ final List<Node> idNodes = getNakedIds(doc, xPath);
+ for (Node node : idNodes) {
+ if (!bindingNodes.contains(node) && !"include".equals(node.getNodeName())) {
+ final Node id = node.getAttributes().getNamedItem("android:id");
+ final String className = getFullViewClassName(node);
+ bundle.createBindingTarget(id.getNodeValue(), className, true, null, null);
+ }
+ }
+ }
+
+ return bundle;
+ }
+
+ private boolean isMergeLayout(Document doc, XPath xPath) throws XPathExpressionException {
+ return !get(doc, xPath, XPATH_MERGE_TAG).isEmpty();
+ }
+
+ private List<Node> getBindingNodes(Document doc, XPath xPath) throws XPathExpressionException {
+ return get(doc, xPath, XPATH_BINDING_ELEMENTS);
+ }
+
+ private List<Node> getVariableNodes(Document doc, XPath xPath) throws XPathExpressionException {
+ return get(doc, xPath, XPATH_VARIABLE_DEFINITIONS);
+ }
+
+ private List<Node> getImportNodes(Document doc, XPath xPath) throws XPathExpressionException {
+ return get(doc, xPath, XPATH_IMPORT_DEFINITIONS);
+ }
+
+ private List<Node> getNakedIds(Document doc, XPath xPath) throws XPathExpressionException {
+ return get(doc, xPath, XPATH_ID_ELEMENTS);
+ }
+
+ private List<Node> get(Document doc, XPath xPath, String pattern)
+ throws XPathExpressionException {
+ final XPathExpression expr = xPath.compile(pattern);
+ return toList((NodeList) expr.evaluate(doc, XPathConstants.NODESET));
+ }
+
+ private List<Node> toList(NodeList nodeList) {
+ List<Node> result = new ArrayList<Node>();
+ for (int i = 0; i < nodeList.getLength(); i ++) {
+ result.add(nodeList.item(i));
+ }
+ return result;
+ }
+
+ private String getFullViewClassName(Node viewNode) {
+ String viewName = viewNode.getNodeName();
+ if ("view".equals(viewName)) {
+ Node classNode = viewNode.getAttributes().getNamedItem("class");
+ if (classNode == null) {
+ L.e("No class attribute for 'view' node");
+ } else {
+ viewName = classNode.getNodeValue();
+ }
+ }
+ if (viewName.indexOf('.') == -1) {
+ if (ObjectUtils.equals(viewName, "View") || ObjectUtils.equals(viewName, "ViewGroup") ||
+ ObjectUtils.equals(viewName, "ViewStub")) {
+ return "android.view." + viewName;
+ }
+ return "android.widget." + viewName;
+ }
+ return viewName;
+ }
+
+ private void stripBindingTags(File xml, String newTag) throws IOException {
+ String res = XmlEditor.INSTANCE$.strip(xml, newTag);
+ if (res != null) {
+ L.d("file %s has changed, overwriting %s", xml.getName(), xml.getAbsolutePath());
+ FileUtils.writeStringToFile(xml, res);
+ }
+ }
+
+ private File stripFileAndGetOriginal(File xml, String binderId)
+ throws ParserConfigurationException, IOException, SAXException,
+ XPathExpressionException {
+ L.d("parsing resource file %s", xml.getAbsolutePath());
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(xml);
+ XPathFactory xPathFactory = XPathFactory.newInstance();
+ XPath xPath = xPathFactory.newXPath();
+ final XPathExpression commentElementExpr = xPath
+ .compile("//comment()[starts-with(., \" From: file:\")][last()]");
+ final NodeList commentElementNodes = (NodeList) commentElementExpr
+ .evaluate(doc, XPathConstants.NODESET);
+ L.d("comment element nodes count %s", commentElementNodes.getLength());
+ if (commentElementNodes.getLength() == 0) {
+ L.d("cannot find comment element to find the actual file");
+ return null;
+ }
+ final Node first = commentElementNodes.item(0);
+ String actualFilePath = first.getNodeValue().substring(" From: file:".length()).trim();
+ L.d("actual file to parse: %s", actualFilePath);
+ File actualFile = new File(actualFilePath);
+ if (!actualFile.canRead()) {
+ L.d("cannot find original, skipping. %s", actualFile.getAbsolutePath());
+ return null;
+ }
+
+ // now if file has any binding expressions, find and delete them
+ // TODO we should rely on namespace to avoid parsing file twice
+ boolean changed = getVariableNodes(doc, xPath).size() > 0 || getImportNodes(doc, xPath).size() > 0;
+ if (changed) {
+ stripBindingTags(xml, binderId);
+ }
+ return actualFile;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/ResourceBundle.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/ResourceBundle.java
new file mode 100644
index 0000000..0cb29e7
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/ResourceBundle.java
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.store;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.util.L;
+import android.databinding.tool.util.ParserHelper;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+/**
+ * This is a serializable class that can keep the result of parsing layout files.
+ */
+public class ResourceBundle implements Serializable {
+
+ private String mAppPackage;
+
+ private HashMap<String, List<LayoutFileBundle>> mLayoutBundles
+ = new HashMap<String, List<LayoutFileBundle>>();
+
+ public ResourceBundle(String appPackage) {
+ mAppPackage = appPackage;
+ }
+
+ public void addLayoutBundle(LayoutFileBundle bundle) {
+ Preconditions.checkArgument(bundle.mFileName != null, "File bundle must have a name");
+ if (!mLayoutBundles.containsKey(bundle.mFileName)) {
+ mLayoutBundles.put(bundle.mFileName, new ArrayList<LayoutFileBundle>());
+ }
+ final List<LayoutFileBundle> bundles = mLayoutBundles.get(bundle.mFileName);
+ for (LayoutFileBundle existing : bundles) {
+ if (existing.equals(bundle)) {
+ L.d("skipping layout bundle %s because it already exists.", bundle);
+ return;
+ }
+ }
+ L.d("adding bundle %s", bundle);
+ bundles.add(bundle);
+ }
+
+ public HashMap<String, List<LayoutFileBundle>> getLayoutBundles() {
+ return mLayoutBundles;
+ }
+
+ public String getAppPackage() {
+ return mAppPackage;
+ }
+
+ public void validateMultiResLayouts() {
+ final Iterable<Map.Entry<String, List<LayoutFileBundle>>> multiResLayouts = Iterables
+ .filter(mLayoutBundles.entrySet(),
+ new Predicate<Map.Entry<String, List<LayoutFileBundle>>>() {
+ @Override
+ public boolean apply(Map.Entry<String, List<LayoutFileBundle>> input) {
+ return input.getValue().size() > 1;
+ }
+ });
+
+ for (Map.Entry<String, List<LayoutFileBundle>> bundles : multiResLayouts) {
+ // validate all ids are in correct view types
+ // and all variables have the same name
+ Map<String, String> variableTypes = new HashMap<String, String>();
+ Map<String, String> importTypes = new HashMap<String, String>();
+
+ for (LayoutFileBundle bundle : bundles.getValue()) {
+ bundle.mHasVariations = true;
+ for (Map.Entry<String, String> variable : bundle.mVariables.entrySet()) {
+ String existing = variableTypes.get(variable.getKey());
+ Preconditions
+ .checkState(existing == null || existing.equals(variable.getValue()),
+ "inconsistent variable types for %s for layout %s",
+ variable.getKey(), bundle.mFileName);
+ variableTypes.put(variable.getKey(), variable.getValue());
+ }
+ for (Map.Entry<String, String> userImport : bundle.mImports.entrySet()) {
+ String existing = importTypes.get(userImport.getKey());
+ Preconditions
+ .checkState(existing == null || existing.equals(userImport.getValue()),
+ "inconsistent variable types for %s for layout %s",
+ userImport.getKey(), bundle.mFileName);
+ importTypes.put(userImport.getKey(), userImport.getValue());
+ }
+ }
+
+ for (LayoutFileBundle bundle : bundles.getValue()) {
+ // now add missing ones to each to ensure they can be referenced
+ L.d("checking for missing variables in %s / %s", bundle.mFileName,
+ bundle.mConfigName);
+ for (Map.Entry<String, String> variable : variableTypes.entrySet()) {
+ if (!bundle.mVariables.containsKey(variable.getKey())) {
+ bundle.mVariables.put(variable.getKey(), variable.getValue());
+ L.d("adding missing variable %s to %s / %s", variable.getKey(),
+ bundle.mFileName, bundle.mConfigName);
+ }
+ }
+ for (Map.Entry<String, String> userImport : importTypes.entrySet()) {
+ if (!bundle.mImports.containsKey(userImport.getKey())) {
+ bundle.mImports.put(userImport.getKey(), userImport.getValue());
+ L.d("adding missing import %s to %s / %s", userImport.getKey(),
+ bundle.mFileName, bundle.mConfigName);
+ }
+ }
+ }
+
+ Set<String> includeBindingIds = new HashSet<String>();
+ Set<String> viewBindingIds = new HashSet<String>();
+ Map<String, String> viewTypes = new HashMap<String, String>();
+ Map<String, String> includes = new HashMap<String, String>();
+ L.d("validating ids for %s", bundles.getKey());
+ for (LayoutFileBundle bundle : bundles.getValue()) {
+ for (BindingTargetBundle target : bundle.mBindingTargetBundles) {
+ L.d("checking %s %s %s", target.getId(), target.mFullClassName, target.isBinder());
+ if (target.isBinder()) {
+ Preconditions.checkState(!viewBindingIds.contains(target.mFullClassName),
+ "Cannot use the same id for a View and an include tag. Error in "
+ + "file %s / %s", bundle.mFileName, bundle.mConfigName);
+ includeBindingIds.add(target.mFullClassName);
+ } else {
+ Preconditions.checkState(!includeBindingIds.contains(target.mFullClassName),
+ "Cannot use the same id for a View and an include tag. Error in "
+ + "file %s / %s", bundle.mFileName, bundle.mConfigName);
+ viewBindingIds.add(target.mFullClassName);
+ }
+ String existingType = viewTypes.get(target.mId);
+ if (existingType == null) {
+ L.d("assigning %s as %s", target.getId(), target.mFullClassName);
+ viewTypes.put(target.mId, target.mFullClassName);
+ if (target.isBinder()) {
+ includes.put(target.mId, target.getIncludedLayout());
+ }
+ } else if (!existingType.equals(target.mFullClassName)) {
+ if (target.isBinder()) {
+ L.d("overriding %s as base binder", target.getId());
+ viewTypes.put(target.mId,
+ "android.databinding.ViewDataBinding");
+ includes.put(target.mId, target.getIncludedLayout());
+ } else {
+ L.d("overriding %s as base view", target.getId());
+ viewTypes.put(target.mId, "android.view.View");
+ }
+ }
+ }
+ }
+
+ for (LayoutFileBundle bundle : bundles.getValue()) {
+ for (Map.Entry<String, String> viewType : viewTypes.entrySet()) {
+ BindingTargetBundle target = bundle.getBindingTargetById(viewType.getKey());
+ if (target == null) {
+ bundle.createBindingTarget(viewType.getKey(), viewType.getValue(), false,
+ null, null).setIncludedLayout(includes.get(viewType.getKey()));
+ } else {
+ L.d("setting interface type on %s (%s) as %s", target.mId, target.mFullClassName, viewType.getValue());
+ target.setInterfaceType(viewType.getValue());
+ }
+ }
+ }
+ }
+ // assign class names to each
+ for (Map.Entry<String, List<LayoutFileBundle>> entry : mLayoutBundles.entrySet()) {
+ for (LayoutFileBundle bundle : entry.getValue()) {
+ final String configName;
+ if (bundle.hasVariations()) {
+ // append configuration specifiers.
+ final String parentFileName = bundle.mDirectory;
+ L.d("parent file for %s is %s", bundle.getFileName(), parentFileName);
+ if ("layout".equals(parentFileName)) {
+ configName = "";
+ } else {
+ configName = ParserHelper.INSTANCE$.toClassName(parentFileName.substring("layout-".length()));
+ }
+ } else {
+ configName = "";
+ }
+ bundle.mConfigName = configName;
+ }
+ }
+ }
+
+ @XmlAccessorType(XmlAccessType.NONE)
+ @XmlRootElement(name="Layout")
+ public static class LayoutFileBundle implements Serializable {
+ @XmlAttribute(name="layout", required = true)
+ public String mFileName;
+ @XmlAttribute(name="modulePackage", required = true)
+ public String mModulePackage;
+ private String mConfigName;
+
+ @XmlAttribute(name="directory", required = true)
+ public String mDirectory;
+ public boolean mHasVariations;
+
+ @XmlElement(name="Variables")
+ @XmlJavaTypeAdapter(NameTypeAdapter.class)
+ public Map<String, String> mVariables = new HashMap<String, String>();
+
+ @XmlElement(name="Imports")
+ @XmlJavaTypeAdapter(NameTypeAdapter.class)
+ public Map<String, String> mImports = new HashMap<String, String>();
+
+ @XmlElementWrapper(name="Targets")
+ @XmlElement(name="Target")
+ public List<BindingTargetBundle> mBindingTargetBundles = new ArrayList<BindingTargetBundle>();
+
+ // for XML binding
+ public LayoutFileBundle() {
+ }
+
+ public LayoutFileBundle(String fileName, String directory, String modulePackage) {
+ mFileName = fileName;
+ mDirectory = directory;
+ mModulePackage = modulePackage;
+ }
+
+ public void addVariable(String name, String type) {
+ mVariables.put(name, type);
+ }
+
+ public void addImport(String alias, String type) {
+ mImports.put(alias, type);
+ }
+
+ public BindingTargetBundle createBindingTarget(String id, String fullClassName,
+ boolean used, String tag, String originalTag) {
+ BindingTargetBundle target = new BindingTargetBundle(id, fullClassName, used, tag,
+ originalTag);
+ mBindingTargetBundles.add(target);
+ return target;
+ }
+
+ public boolean isEmpty() {
+ return mVariables.isEmpty() && mImports.isEmpty() && mBindingTargetBundles.isEmpty();
+ }
+
+ public BindingTargetBundle getBindingTargetById(String key) {
+ for (BindingTargetBundle target : mBindingTargetBundles) {
+ if (key.equals(target.mId)) {
+ return target;
+ }
+ }
+ return null;
+ }
+
+ public String getFileName() {
+ return mFileName;
+ }
+
+ public String getConfigName() {
+ return mConfigName;
+ }
+
+ public String getDirectory() {
+ return mDirectory;
+ }
+
+ public boolean hasVariations() {
+ return mHasVariations;
+ }
+
+ public Map<String, String> getVariables() {
+ return mVariables;
+ }
+
+ public Map<String, String> getImports() {
+ return mImports;
+ }
+
+ public List<BindingTargetBundle> getBindingTargetBundles() {
+ return mBindingTargetBundles;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ LayoutFileBundle bundle = (LayoutFileBundle) o;
+
+ if (mConfigName != null ? !mConfigName.equals(bundle.mConfigName)
+ : bundle.mConfigName != null) {
+ return false;
+ }
+ if (mDirectory != null ? !mDirectory.equals(bundle.mDirectory)
+ : bundle.mDirectory != null) {
+ return false;
+ }
+ if (mFileName != null ? !mFileName.equals(bundle.mFileName)
+ : bundle.mFileName != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mFileName != null ? mFileName.hashCode() : 0;
+ result = 31 * result + (mConfigName != null ? mConfigName.hashCode() : 0);
+ result = 31 * result + (mDirectory != null ? mDirectory.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "LayoutFileBundle{" +
+ "mHasVariations=" + mHasVariations +
+ ", mDirectory='" + mDirectory + '\'' +
+ ", mConfigName='" + mConfigName + '\'' +
+ ", mModulePackage='" + mModulePackage + '\'' +
+ ", mFileName='" + mFileName + '\'' +
+ '}';
+ }
+
+ public String getModulePackage() {
+ return mModulePackage;
+ }
+ }
+
+ @XmlAccessorType(XmlAccessType.NONE)
+ public static class MarshalledNameType {
+ @XmlAttribute(name="type", required = true)
+ public String type;
+
+ @XmlAttribute(name="name", required = true)
+ public String name;
+ }
+
+ public static class MarshalledMapType {
+ public List<MarshalledNameType> entries;
+ }
+
+ @XmlAccessorType(XmlAccessType.NONE)
+ public static class BindingTargetBundle implements Serializable {
+ // public for XML serialization
+
+ @XmlAttribute(name="id")
+ public String mId;
+ @XmlAttribute(name="tag", required = true)
+ public String mTag;
+ @XmlAttribute(name="originalTag")
+ public String mOriginalTag;
+ @XmlAttribute(name="boundClass", required = true)
+ public String mFullClassName;
+ public boolean mUsed = true;
+ @XmlElementWrapper(name="Expressions")
+ @XmlElement(name="Expression")
+ public List<BindingBundle> mBindingBundleList = new ArrayList<BindingBundle>();
+ @XmlAttribute(name="include")
+ public String mIncludedLayout;
+ private String mInterfaceType;
+
+ // For XML serialization
+ public BindingTargetBundle() {}
+
+ public BindingTargetBundle(String id, String fullClassName, boolean used,
+ String tag, String originalTag) {
+ mId = id;
+ mFullClassName = fullClassName;
+ mUsed = used;
+ mTag = tag;
+ mOriginalTag = originalTag;
+ }
+
+ public void addBinding(String name, String expr) {
+ mBindingBundleList.add(new BindingBundle(name, expr));
+ }
+
+ public void setIncludedLayout(String includedLayout) {
+ mIncludedLayout = includedLayout;
+ }
+
+ public String getIncludedLayout() {
+ return mIncludedLayout;
+ }
+
+ public boolean isBinder() {
+ return mIncludedLayout != null;
+ }
+
+ public void setInterfaceType(String interfaceType) {
+ mInterfaceType = interfaceType;
+ }
+
+ public String getId() {
+ return mId;
+ }
+
+ public String getTag() {
+ return mTag;
+ }
+
+ public String getOriginalTag() {
+ return mOriginalTag;
+ }
+
+ public String getFullClassName() {
+ return mFullClassName;
+ }
+
+ public boolean isUsed() {
+ return mUsed;
+ }
+
+ public List<BindingBundle> getBindingBundleList() {
+ return mBindingBundleList;
+ }
+
+ public String getInterfaceType() {
+ return mInterfaceType;
+ }
+
+ @XmlAccessorType(XmlAccessType.NONE)
+ public static class BindingBundle implements Serializable {
+
+ private String mName;
+ private String mExpr;
+
+ public BindingBundle() {}
+
+ public BindingBundle(String name, String expr) {
+ mName = name;
+ mExpr = expr;
+ }
+
+ @XmlAttribute(name="attribute", required=true)
+ public String getName() {
+ return mName;
+ }
+
+ @XmlAttribute(name="text", required=true)
+ public String getExpr() {
+ return mExpr;
+ }
+
+ public void setName(String name) {
+ mName = name;
+ }
+
+ public void setExpr(String expr) {
+ mExpr = expr;
+ }
+ }
+ }
+
+ private final static class NameTypeAdapter
+ extends XmlAdapter<MarshalledMapType, Map<String, String>> {
+
+ @Override
+ public HashMap<String, String> unmarshal(MarshalledMapType v) throws Exception {
+ HashMap<String, String> map = new HashMap<String, String>();
+ if (v.entries != null) {
+ for (MarshalledNameType entry : v.entries) {
+ map.put(entry.name, entry.type);
+ }
+ }
+ return map;
+ }
+
+ @Override
+ public MarshalledMapType marshal(Map<String, String> v) throws Exception {
+ if (v.isEmpty()) {
+ return null;
+ }
+ MarshalledMapType marshalled = new MarshalledMapType();
+ marshalled.entries = new ArrayList<MarshalledNameType>();
+ for (String name : v.keySet()) {
+ MarshalledNameType nameType = new MarshalledNameType();
+ nameType.name = name;
+ nameType.type = v.get(name);
+ marshalled.entries.add(nameType);
+ }
+ return marshalled;
+ }
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/SetterStore.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/SetterStore.java
new file mode 100644
index 0000000..3a53c2c
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/store/SetterStore.java
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.tool.store;
+
+import org.apache.commons.lang3.StringUtils;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.ModelMethod;
+import android.databinding.tool.util.GenerationalClassUtil;
+import android.databinding.tool.util.L;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+
+public class SetterStore {
+
+ public static final String SETTER_STORE_FILE_EXT = "-setter_store.bin";
+
+ private static SetterStore sStore;
+
+ private final IntermediateV1 mStore;
+ private final ModelAnalyzer mClassAnalyzer;
+
+ private SetterStore(ModelAnalyzer modelAnalyzer, IntermediateV1 store) {
+ mClassAnalyzer = modelAnalyzer;
+ mStore = store;
+ }
+
+ public static SetterStore get(ModelAnalyzer modelAnalyzer) {
+ if (sStore == null) {
+ sStore = load(modelAnalyzer, SetterStore.class.getClassLoader());
+ }
+ return sStore;
+ }
+
+ private static SetterStore load(ModelAnalyzer modelAnalyzer, ClassLoader classLoader) {
+ IntermediateV1 store = new IntermediateV1();
+ List<Intermediate> previousStores = GenerationalClassUtil
+ .loadObjects(classLoader,
+ new GenerationalClassUtil.ExtensionFilter(SETTER_STORE_FILE_EXT));
+ for (Intermediate intermediate : previousStores) {
+ merge(store, intermediate);
+ }
+ return new SetterStore(modelAnalyzer, store);
+ }
+
+ public void addRenamedMethod(String attribute, String declaringClass, String method,
+ TypeElement declaredOn) {
+ HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute);
+ if (renamed == null) {
+ renamed = new HashMap<String, MethodDescription>();
+ mStore.renamedMethods.put(attribute, renamed);
+ }
+ MethodDescription methodDescription =
+ new MethodDescription(declaredOn.getQualifiedName().toString(), method);
+ L.d("STORE addmethod desc %s", methodDescription);
+ renamed.put(declaringClass, methodDescription);
+ }
+
+ public void addBindingAdapter(String attribute, ExecutableElement bindingMethod) {
+ L.d("STORE addBindingAdapter %s %s", attribute, bindingMethod);
+ HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute);
+
+ if (adapters == null) {
+ adapters = new HashMap<AccessorKey, MethodDescription>();
+ mStore.adapterMethods.put(attribute, adapters);
+ }
+ List<? extends VariableElement> parameters = bindingMethod.getParameters();
+ String view = getQualifiedName(parameters.get(0).asType());
+ String value = getQualifiedName(parameters.get(1).asType());
+
+ AccessorKey key = new AccessorKey(view, value);
+ if (adapters.containsKey(key)) {
+ throw new IllegalArgumentException("Already exists!");
+ }
+
+ adapters.put(key, new MethodDescription(bindingMethod));
+ }
+
+ public void addUntaggableTypes(String[] typeNames, TypeElement declaredOn) {
+ L.d("STORE addUntaggableTypes %s %s", Arrays.toString(typeNames), declaredOn);
+ String declaredType = declaredOn.getQualifiedName().toString();
+ for (String type : typeNames) {
+ mStore.untaggableTypes.put(type, declaredType);
+ }
+ }
+
+ private static String getQualifiedName(TypeMirror type) {
+ switch (type.getKind()) {
+ case BOOLEAN:
+ case BYTE:
+ case SHORT:
+ case INT:
+ case LONG:
+ case CHAR:
+ case FLOAT:
+ case DOUBLE:
+ case VOID:
+ return type.toString();
+ case ARRAY:
+ return getQualifiedName(((ArrayType) type).getComponentType()) + "[]";
+ case DECLARED:
+ return ((TypeElement) ((DeclaredType) type).asElement()).getQualifiedName()
+ .toString();
+ default:
+ return "-- no type --";
+ }
+ }
+
+ public void addConversionMethod(ExecutableElement conversionMethod) {
+ L.d("STORE addConversionMethod %s", conversionMethod);
+ List<? extends VariableElement> parameters = conversionMethod.getParameters();
+ String fromType = getQualifiedName(parameters.get(0).asType());
+ String toType = getQualifiedName(conversionMethod.getReturnType());
+ MethodDescription methodDescription = new MethodDescription(conversionMethod);
+ HashMap<String, MethodDescription> convertTo = mStore.conversionMethods.get(fromType);
+ if (convertTo == null) {
+ convertTo = new HashMap<String, MethodDescription>();
+ mStore.conversionMethods.put(fromType, convertTo);
+ }
+ convertTo.put(toType, methodDescription);
+ }
+
+ public void clear(Set<String> classes) {
+ ArrayList<AccessorKey> removedAccessorKeys = new ArrayList<AccessorKey>();
+ for (HashMap<AccessorKey, MethodDescription> adapters : mStore.adapterMethods.values()) {
+ for (AccessorKey key : adapters.keySet()) {
+ MethodDescription description = adapters.get(key);
+ if (classes.contains(description.type)) {
+ removedAccessorKeys.add(key);
+ }
+ }
+ removeFromMap(adapters, removedAccessorKeys);
+ }
+
+ ArrayList<String> removedRenamed = new ArrayList<String>();
+ for (HashMap<String, MethodDescription> renamed : mStore.renamedMethods.values()) {
+ for (String key : renamed.keySet()) {
+ if (classes.contains(renamed.get(key).type)) {
+ removedRenamed.add(key);
+ }
+ }
+ removeFromMap(renamed, removedRenamed);
+ }
+
+ ArrayList<String> removedConversions = new ArrayList<String>();
+ for (HashMap<String, MethodDescription> convertTos : mStore.conversionMethods.values()) {
+ for (String toType : convertTos.keySet()) {
+ MethodDescription methodDescription = convertTos.get(toType);
+ if (classes.contains(methodDescription.type)) {
+ removedConversions.add(toType);
+ }
+ }
+ removeFromMap(convertTos, removedConversions);
+ }
+
+ ArrayList<String> removedUntaggable = new ArrayList<String>();
+ for (String typeName : mStore.untaggableTypes.keySet()) {
+ if (classes.contains(mStore.untaggableTypes.get(typeName))) {
+ removedUntaggable.add(typeName);
+ }
+ }
+ removeFromMap(mStore.untaggableTypes, removedUntaggable);
+ }
+
+ private static <K, V> void removeFromMap(Map<K, V> map, List<K> keys) {
+ for (K key : keys) {
+ map.remove(key);
+ }
+ keys.clear();
+ }
+
+ public void write(String projectPackage, ProcessingEnvironment processingEnvironment)
+ throws IOException {
+ GenerationalClassUtil.writeIntermediateFile(processingEnvironment,
+ projectPackage, projectPackage + SETTER_STORE_FILE_EXT, mStore);
+ }
+
+ public SetterCall getSetterCall(String attribute, ModelClass viewType,
+ ModelClass valueType, Map<String, String> imports) {
+ if (!attribute.startsWith("android:")) {
+ int colon = attribute.indexOf(':');
+ if (colon >= 0) {
+ attribute = attribute.substring(colon + 1);
+ }
+ }
+ SetterCall setterCall = null;
+ MethodDescription conversionMethod = null;
+ if (viewType != null) {
+ HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute);
+ ModelMethod bestSetterMethod = getBestSetter(viewType, valueType, attribute, imports);
+ ModelClass bestViewType = null;
+ ModelClass bestValueType = null;
+ if (bestSetterMethod != null) {
+ bestViewType = bestSetterMethod.getDeclaringClass();
+ bestValueType = bestSetterMethod.getParameterTypes()[0];
+ setterCall = new ModelMethodSetter(bestSetterMethod);
+ }
+
+ if (adapters != null) {
+ for (AccessorKey key : adapters.keySet()) {
+ try {
+ ModelClass adapterViewType = mClassAnalyzer
+ .findClass(key.viewType, imports);
+ if (adapterViewType.isAssignableFrom(viewType)) {
+ try {
+ ModelClass adapterValueType = mClassAnalyzer
+ .findClass(key.valueType, imports);
+ boolean isBetterView = bestViewType == null ||
+ bestValueType.isAssignableFrom(adapterValueType);
+ if (isBetterParameter(valueType, adapterValueType, bestValueType,
+ isBetterView, imports)) {
+ bestViewType = adapterViewType;
+ bestValueType = adapterValueType;
+ MethodDescription adapter = adapters.get(key);
+ setterCall = new AdapterSetter(adapter);
+ }
+
+ } catch (Exception e) {
+ L.e(e, "Unknown class: %s", key.valueType);
+ }
+ }
+ } catch (Exception e) {
+ L.e(e, "Unknown class: %s", key.viewType);
+ }
+ }
+ }
+
+ conversionMethod = getConversionMethod(valueType, bestValueType, imports);
+ if (valueType.isObject() && setterCall != null && bestValueType.isNullable()) {
+ setterCall.setCast(bestValueType);
+ }
+ }
+ if (setterCall == null) {
+ setterCall = new DummySetter(getDefaultSetter(attribute));
+ // might be an include tag etc. just note it and continue.
+ L.d("Cannot find the setter for attribute " + attribute + ". might be an include file,"
+ + " moving on.");
+ }
+ setterCall.setConverter(conversionMethod);
+ return setterCall;
+ }
+
+ public boolean isUntaggable(String viewType) {
+ return mStore.untaggableTypes.containsKey(viewType);
+ }
+
+ private ModelMethod getBestSetter(ModelClass viewType, ModelClass argumentType,
+ String attribute, Map<String, String> imports) {
+ List<String> setterCandidates = new ArrayList<String>();
+ HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute);
+ if (renamed != null) {
+ for (String className : renamed.keySet()) {
+ try {
+ ModelClass renamedViewType = mClassAnalyzer.findClass(className, imports);
+ if (renamedViewType.isAssignableFrom(viewType)) {
+ setterCandidates.add(renamed.get(className).method);
+ break;
+ }
+ } catch (Exception e) {
+ //printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + className);
+ }
+ }
+ }
+ setterCandidates.add(getDefaultSetter(attribute));
+ setterCandidates.add(trimAttributeNamespace(attribute));
+
+ ModelMethod bestMethod = null;
+ ModelClass bestParameterType = null;
+ List<ModelClass> args = new ArrayList<ModelClass>();
+ args.add(argumentType);
+ for (String name : setterCandidates) {
+ ModelMethod[] methods = viewType.getMethods(name, 1);
+
+ for (ModelMethod method : methods) {
+ ModelClass[] parameterTypes = method.getParameterTypes();
+ ModelClass param = parameterTypes[0];
+ if (method.isVoid() &&
+ isBetterParameter(argumentType, param, bestParameterType, true, imports)) {
+ bestParameterType = param;
+ bestMethod = method;
+ }
+ }
+ }
+ return bestMethod;
+
+ }
+
+ private static String trimAttributeNamespace(String attribute) {
+ final int colonIndex = attribute.indexOf(':');
+ return colonIndex == -1 ? attribute : attribute.substring(colonIndex + 1);
+ }
+
+ private static String getDefaultSetter(String attribute) {
+ return "set" + StringUtils.capitalize(trimAttributeNamespace(attribute));
+ }
+
+ private boolean isBetterParameter(ModelClass argument, ModelClass parameter,
+ ModelClass oldParameter, boolean isBetterViewTypeMatch, Map<String, String> imports) {
+ // Right view type. Check the value
+ if (!isBetterViewTypeMatch && oldParameter.equals(argument)) {
+ return false;
+ } else if (argument.equals(parameter)) {
+ // Exact match
+ return true;
+ } else if (!isBetterViewTypeMatch &&
+ ModelMethod.isBoxingConversion(oldParameter, argument)) {
+ return false;
+ } else if (ModelMethod.isBoxingConversion(parameter, argument)) {
+ // Boxing/unboxing is second best
+ return true;
+ } else {
+ int oldConversionLevel = ModelMethod.getImplicitConversionLevel(oldParameter);
+ if (ModelMethod.isImplicitConversion(argument, parameter)) {
+ // Better implicit conversion
+ int conversionLevel = ModelMethod.getImplicitConversionLevel(parameter);
+ return oldConversionLevel < 0 || conversionLevel < oldConversionLevel;
+ } else if (oldConversionLevel >= 0) {
+ return false;
+ } else if (parameter.isAssignableFrom(argument)) {
+ // Right type, see if it is better than the current best match.
+ if (oldParameter == null) {
+ return true;
+ } else {
+ return oldParameter.isAssignableFrom(parameter);
+ }
+ } else {
+ MethodDescription conversionMethod = getConversionMethod(argument, parameter,
+ imports);
+ if (conversionMethod != null) {
+ return true;
+ }
+ if (getConversionMethod(argument, oldParameter, imports) != null) {
+ return false;
+ }
+ return argument.isObject() && !parameter.isPrimitive();
+ }
+ }
+ }
+
+ private MethodDescription getConversionMethod(ModelClass from, ModelClass to,
+ Map<String, String> imports) {
+ if (from != null && to != null) {
+ for (String fromClassName : mStore.conversionMethods.keySet()) {
+ try {
+ ModelClass convertFrom = mClassAnalyzer.findClass(fromClassName, imports);
+ if (canUseForConversion(from, convertFrom)) {
+ HashMap<String, MethodDescription> conversion =
+ mStore.conversionMethods.get(fromClassName);
+ for (String toClassName : conversion.keySet()) {
+ try {
+ ModelClass convertTo = mClassAnalyzer.findClass(toClassName,
+ imports);
+ if (canUseForConversion(convertTo, to)) {
+ return conversion.get(toClassName);
+ }
+ } catch (Exception e) {
+ L.d(e, "Unknown class: %s", toClassName);
+ }
+ }
+ }
+ } catch (Exception e) {
+ L.d(e, "Unknown class: %s", fromClassName);
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean canUseForConversion(ModelClass from, ModelClass to) {
+ return from.equals(to) || ModelMethod.isBoxingConversion(from, to) ||
+ to.isAssignableFrom(from);
+ }
+
+ private static void merge(IntermediateV1 store, Intermediate dumpStore) {
+ IntermediateV1 intermediateV1 = (IntermediateV1) dumpStore.upgrade();
+ merge(store.adapterMethods, intermediateV1.adapterMethods);
+ merge(store.renamedMethods, intermediateV1.renamedMethods);
+ merge(store.conversionMethods, intermediateV1.conversionMethods);
+ store.untaggableTypes.putAll(intermediateV1.untaggableTypes);
+ }
+
+ private static <K, V> void merge(HashMap<K, HashMap<V, MethodDescription>> first,
+ HashMap<K, HashMap<V, MethodDescription>> second) {
+ for (K key : second.keySet()) {
+ HashMap<V, MethodDescription> firstVals = first.get(key);
+ HashMap<V, MethodDescription> secondVals = second.get(key);
+ if (firstVals == null) {
+ first.put(key, secondVals);
+ } else {
+ for (V key2 : secondVals.keySet()) {
+ if (!firstVals.containsKey(key2)) {
+ firstVals.put(key2, secondVals.get(key2));
+ }
+ }
+ }
+ }
+ }
+
+ private static class MethodDescription implements Serializable {
+
+ private static final long serialVersionUID = 1;
+
+ public final String type;
+
+ public final String method;
+
+ public MethodDescription(String type, String method) {
+ this.type = type;
+ this.method = method;
+ L.d("BINARY created method desc 1 %s %s", type, method );
+ }
+
+ public MethodDescription(ExecutableElement method) {
+ TypeElement enclosingClass = (TypeElement) method.getEnclosingElement();
+ this.type = enclosingClass.getQualifiedName().toString();
+ this.method = method.getSimpleName().toString();
+ L.d("BINARY created method desc 2 %s %s, %s", type, this.method, method);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof MethodDescription) {
+ MethodDescription that = (MethodDescription) obj;
+ return that.type.equals(this.type) && that.method.equals(this.method);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, method);
+ }
+
+ @Override
+ public String toString() {
+ return type + "." + method + "()";
+ }
+ }
+
+ private static class AccessorKey implements Serializable {
+
+ private static final long serialVersionUID = 1;
+
+ public final String viewType;
+
+ public final String valueType;
+
+ public AccessorKey(String viewType, String valueType) {
+ this.viewType = viewType;
+ this.valueType = valueType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(viewType, valueType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof AccessorKey) {
+ AccessorKey that = (AccessorKey) obj;
+ return viewType.equals(that.valueType) && valueType.equals(that.valueType);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "AK(" + viewType + ", " + valueType + ")";
+ }
+ }
+
+ private interface Intermediate extends Serializable {
+ Intermediate upgrade();
+ }
+
+ private static class IntermediateV1 implements Serializable, Intermediate {
+ private static final long serialVersionUID = 1;
+ public final HashMap<String, HashMap<AccessorKey, MethodDescription>> adapterMethods =
+ new HashMap<String, HashMap<AccessorKey, MethodDescription>>();
+ public final HashMap<String, HashMap<String, MethodDescription>> renamedMethods =
+ new HashMap<String, HashMap<String, MethodDescription>>();
+ public final HashMap<String, HashMap<String, MethodDescription>> conversionMethods =
+ new HashMap<String, HashMap<String, MethodDescription>>();
+ public final HashMap<String, String> untaggableTypes = new HashMap<String, String>();
+
+ public IntermediateV1() {
+ }
+
+ @Override
+ public Intermediate upgrade() {
+ return this;
+ }
+ }
+
+ public static class DummySetter extends SetterCall {
+ private String mMethodName;
+
+ public DummySetter(String methodName) {
+ mMethodName = methodName;
+ }
+
+ @Override
+ public String toJavaInternal(String viewExpression, String valueExpression) {
+ return viewExpression + "." + mMethodName + "(" + valueExpression + ")";
+ }
+
+ @Override
+ public int getMinApi() {
+ return 1;
+ }
+ }
+
+ public static class AdapterSetter extends SetterCall {
+ final MethodDescription mAdapter;
+
+ public AdapterSetter(MethodDescription adapter) {
+ mAdapter = adapter;
+ }
+
+ @Override
+ public String toJavaInternal(String viewExpression, String valueExpression) {
+ return mAdapter.type + "." + mAdapter.method + "(" + viewExpression + ", " +
+ mCastString + valueExpression + ")";
+ }
+
+ @Override
+ public int getMinApi() {
+ return 1;
+ }
+ }
+
+ public static class ModelMethodSetter extends SetterCall {
+ final ModelMethod mModelMethod;
+
+ public ModelMethodSetter(ModelMethod modelMethod) {
+ mModelMethod = modelMethod;
+ }
+
+ @Override
+ public String toJavaInternal(String viewExpression, String valueExpression) {
+ return viewExpression + "." + mModelMethod.getName() + "(" + mCastString +
+ valueExpression + ")";
+ }
+
+ @Override
+ public int getMinApi() {
+ return mModelMethod.getMinApi();
+ }
+ }
+
+ public static abstract class SetterCall {
+ private MethodDescription mConverter;
+ protected String mCastString = "";
+
+ public SetterCall() {
+ }
+
+ public void setConverter(MethodDescription converter) {
+ mConverter = converter;
+ }
+
+ protected abstract String toJavaInternal(String viewExpression, String converted);
+
+ public final String toJava(String viewExpression, String valueExpression) {
+ return toJavaInternal(viewExpression, convertValue(valueExpression));
+ }
+
+ protected String convertValue(String valueExpression) {
+ return mConverter == null ? valueExpression :
+ mConverter.type + "." + mConverter.method + "(" + valueExpression + ")";
+ }
+
+ abstract public int getMinApi();
+
+ public void setCast(ModelClass castTo) {
+ mCastString = "(" + castTo.toJavaCode() + ") ";
+ }
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/util/GenerationalClassUtil.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/util/GenerationalClassUtil.java
new file mode 100644
index 0000000..e7373ac
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/util/GenerationalClassUtil.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.util;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.filefilter.IOFileFilter;
+import org.apache.commons.io.filefilter.TrueFileFilter;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+
+/**
+ * A utility class that helps adding build specific objects to the jar file
+ * and their extraction later on.
+ */
+public class GenerationalClassUtil {
+ public static <T extends Serializable> List<T> loadObjects(ClassLoader classLoader, Filter filter) {
+ final List<T> result = new ArrayList<T>();
+ if (!(classLoader instanceof URLClassLoader)) {
+ L.d("class loader is not url class loader (%s). skipping.", classLoader.getClass());
+ return result;
+ }
+ final URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
+ for (URL url : urlClassLoader.getURLs()) {
+ L.d("checking url %s for intermediate data", url);
+ try {
+ final File file = new File(url.toURI());
+ if (!file.exists()) {
+ L.d("cannot load file for %s", url);
+ continue;
+ }
+ if (file.isDirectory()) {
+ // probably exported classes dir.
+ loadFromDirectory(filter, result, file);
+ } else {
+ // assume it is a zip file
+ loadFomZipFile(filter, result, file);
+ }
+ } catch (IOException e) {
+ L.d("cannot open zip file from %s", url);
+ } catch (URISyntaxException e) {
+ L.d("cannot open zip file from %s", url);
+ }
+ }
+ return result;
+ }
+
+ private static <T extends Serializable> void loadFromDirectory(final Filter filter, List<T> result,
+ File directory) {
+ //noinspection unchecked
+ Collection<File> files = FileUtils.listFiles(directory, new IOFileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return filter.accept(file.getName());
+ }
+
+ @Override
+ public boolean accept(File dir, String name) {
+ return filter.accept(name);
+ }
+ }, TrueFileFilter.INSTANCE);
+ for (File file : files) {
+ InputStream inputStream = null;
+ try {
+ inputStream = FileUtils.openInputStream(file);
+ T item = fromInputStream(result, inputStream);
+ L.d("loaded item %s from file", item);
+ if (item != null) {
+ result.add(item);
+ }
+ } catch (IOException e) {
+ L.e(e, "Could not merge in Bindables from %s", file.getAbsolutePath());
+ } catch (ClassNotFoundException e) {
+ L.e(e, "Could not read Binding properties intermediate file. %s", file.getAbsolutePath());
+ } finally {
+ IOUtils.closeQuietly(inputStream);
+ }
+ }
+ }
+
+ private static <T extends Serializable> void loadFomZipFile(Filter filter,
+ List<T> result, File file) throws IOException {
+ ZipFile zipFile = new ZipFile(file);
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ if (!filter.accept(entry.getName())) {
+ continue;
+ }
+ L.d("loading data from file %s", entry.getName());
+ InputStream inputStream = null;
+ try {
+ inputStream = zipFile.getInputStream(entry);
+ T item = fromInputStream(result, inputStream);
+ L.d("loaded item %s from zip file", item);
+ if (item != null) {
+ result.add(item);
+ }
+ } catch (IOException e) {
+ L.e(e, "Could not merge in Bindables from %s", file.getAbsolutePath());
+ } catch (ClassNotFoundException e) {
+ L.e(e, "Could not read Binding properties intermediate file. %s", file.getAbsolutePath());
+ } finally {
+ IOUtils.closeQuietly(inputStream);
+ }
+ }
+ }
+
+ private static <T extends Serializable> T fromInputStream(List<T> result,
+ InputStream inputStream) throws IOException, ClassNotFoundException {
+ ObjectInputStream in = new ObjectInputStream(inputStream);
+ return (T) in.readObject();
+
+ }
+
+ public static void writeIntermediateFile(ProcessingEnvironment processingEnv,
+ String packageName, String fileName, Serializable object) {
+ ObjectOutputStream oos = null;
+ try {
+ FileObject intermediate = processingEnv.getFiler().createResource(
+ StandardLocation.CLASS_OUTPUT, packageName,
+ fileName);
+ OutputStream ios = intermediate.openOutputStream();
+ oos = new ObjectOutputStream(ios);
+ oos.writeObject(object);
+ oos.close();
+ L.d("wrote intermediate bindable file %s %s", packageName, fileName);
+ } catch (IOException e) {
+ L.e(e, "Could not write to intermediate file: %s", fileName);
+ } finally {
+ IOUtils.closeQuietly(oos);
+ }
+ }
+
+
+ public static interface Filter {
+ public boolean accept(String entryName);
+ }
+
+ public static class ExtensionFilter implements Filter {
+ private final String mExtension;
+ public ExtensionFilter(String extension) {
+ mExtension = extension;
+ }
+
+ @Override
+ public boolean accept(String entryName) {
+ return entryName.endsWith(mExtension);
+ }
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/util/L.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/util/L.java
new file mode 100644
index 0000000..5439f59
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/util/L.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.util;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.annotation.AnnotationAnalyzer;
+
+import javax.tools.Diagnostic;
+
+public class L {
+
+ public static void d(String msg, Object... args) {
+ printMessage(Diagnostic.Kind.NOTE, String.format(msg, args));
+ }
+
+ public static void d(Throwable t, String msg, Object... args) {
+ printMessage(Diagnostic.Kind.NOTE,
+ String.format(msg, args) + " " + ExceptionUtils.getStackTrace(t));
+ }
+
+ public static void e(String msg, Object... args) {
+ printMessage(Diagnostic.Kind.ERROR, String.format(msg, args));
+ }
+
+ public static void e(Throwable t, String msg, Object... args) {
+ printMessage(Diagnostic.Kind.ERROR,
+ String.format(msg, args) + " " + ExceptionUtils.getStackTrace(t));
+ }
+
+ private static void printMessage(Diagnostic.Kind kind, String message) {
+ ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
+ System.out.println("[" + kind.name() + "]: " + message);
+ if (modelAnalyzer instanceof AnnotationAnalyzer) {
+ ((AnnotationAnalyzer) modelAnalyzer).getProcessingEnv().getMessager()
+ .printMessage(kind, message);
+ if (kind == Diagnostic.Kind.ERROR) {
+ throw new RuntimeException("failure, see logs for details.\n" + message);
+ }
+ } else {
+
+ if (kind == Diagnostic.Kind.ERROR) {
+ throw new RuntimeException(message);
+ }
+ }
+ }
+
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/writer/AnnotationJavaFileWriter.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/writer/AnnotationJavaFileWriter.java
new file mode 100644
index 0000000..4089905
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/writer/AnnotationJavaFileWriter.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.tool.writer;
+
+import org.apache.commons.io.IOUtils;
+
+import android.databinding.tool.util.L;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.tools.JavaFileObject;
+
+public class AnnotationJavaFileWriter extends JavaFileWriter {
+ private final ProcessingEnvironment mProcessingEnvironment;
+
+ public AnnotationJavaFileWriter(ProcessingEnvironment processingEnvironment) {
+ mProcessingEnvironment = processingEnvironment;
+ }
+
+ @Override
+ public void writeToFile(String canonicalName, String contents) {
+ Writer writer = null;
+ try {
+ L.d("writing file %s", canonicalName);
+ JavaFileObject javaFileObject =
+ mProcessingEnvironment.getFiler().createSourceFile(canonicalName);
+ writer = javaFileObject.openWriter();
+ writer.write(contents);
+ } catch (IOException e) {
+ L.e(e, "Could not write to %s", canonicalName);
+ } finally {
+ if (writer != null) {
+ IOUtils.closeQuietly(writer);
+ }
+ }
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/writer/FlagSet.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/writer/FlagSet.java
new file mode 100644
index 0000000..a5e799e
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/writer/FlagSet.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.writer;
+
+import java.util.Arrays;
+import java.util.BitSet;
+
+/**
+ * Used for code generation. A BitSet can be converted into a flag set,
+ * which is basically a list of longs that can be divided into pieces.
+ */
+public class FlagSet {
+ public static final int sBucketSize = 64;// long
+ public final String type;
+ public final long[] buckets;
+ private String mLocalName;
+ private boolean mIsDynamic = false;
+
+ public FlagSet(BitSet bitSet, int bucketCount) {
+ buckets = new long[bucketCount];
+ for (int i = bitSet.nextSetBit(0);
+ i != -1; i = bitSet.nextSetBit(i + 1)) {
+ buckets[i / sBucketSize] |= 1 << (i % sBucketSize);
+ }
+ type = "long";
+ }
+
+ public FlagSet(long[] buckets) {
+ this.buckets = new long[buckets.length];
+ System.arraycopy(buckets, 0, this.buckets, 0, buckets.length);
+ type = "long";
+ }
+
+ public FlagSet(long[] buckets, int minBucketCount) {
+ this.buckets = new long[Math.max(buckets.length, minBucketCount)];
+ System.arraycopy(buckets, 0, this.buckets, 0, buckets.length);
+ type = "long";
+ }
+
+ public FlagSet(int... bits) {
+ int max = 0;
+ for (int i = 0 ; i < bits.length; i ++) {
+ max = Math.max(i, bits[i]);
+ }
+ buckets = new long[1 + (max / sBucketSize)];
+ for (int x = 0 ; x < bits.length; x ++) {
+ final int i = bits[x];
+ buckets[i / sBucketSize] |= 1 << (i % sBucketSize);
+ }
+ type = "long";
+ }
+
+ public boolean intersect(FlagSet other, int bucketIndex) {
+ return (buckets[bucketIndex] & other.buckets[bucketIndex]) != 0;
+ }
+
+ public String getLocalName() {
+ return mLocalName;
+ }
+
+ public void setLocalName(String localName) {
+ mLocalName = localName;
+ }
+
+ public boolean hasLocalName() {
+ return mLocalName != null;
+ }
+
+ public boolean isDynamic() {
+ return mIsDynamic;
+ }
+
+ public void setDynamic(boolean isDynamic) {
+ mIsDynamic = isDynamic;
+ }
+
+ public FlagSet andNot(FlagSet other) {
+ FlagSet result = new FlagSet(buckets);
+ final int min = Math.min(buckets.length, other.buckets.length);
+ for (int i = 0; i < min; i ++) {
+ result.buckets[i] &= ~(other.buckets[i]);
+ }
+ return result;
+ }
+
+ public FlagSet or(FlagSet other) {
+ final FlagSet result = new FlagSet(buckets, other.buckets.length);
+ for (int i = 0; i < other.buckets.length; i ++) {
+ result.buckets[i] |= other.buckets[i];
+ }
+ return result;
+ }
+
+ public boolean isEmpty() {
+ for (int i = 0; i < buckets.length; i ++) {
+ if (buckets[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < buckets.length; i ++) {
+ sb.append(Long.toBinaryString(buckets[i])).append(" ");
+ }
+ return sb.toString();
+ }
+
+ private long getBucket(int bucketIndex) {
+ if (bucketIndex >= buckets.length) {
+ return 0;
+ }
+ return buckets[bucketIndex];
+ }
+
+ public boolean bitsEqual(FlagSet other) {
+ final int max = Math.max(buckets.length, other.buckets.length);
+ for (int i = 0; i < max; i ++) {
+ if (getBucket(i) != other.getBucket(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/writer/JavaFileWriter.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/writer/JavaFileWriter.java
new file mode 100644
index 0000000..d4e0565
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/writer/JavaFileWriter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.writer;
+
+import org.apache.commons.io.FileUtils;
+
+import android.databinding.tool.util.L;
+
+import java.io.File;
+import java.io.IOException;
+
+public abstract class JavaFileWriter {
+ public abstract void writeToFile(String canonicalName, String contents);
+ public void writeToFile(File exactPath, String contents) {
+ File parent = exactPath.getParentFile();
+ parent.mkdirs();
+ try {
+ L.d("writing file %s", exactPath.getAbsoluteFile());
+ FileUtils.writeStringToFile(exactPath, contents);
+ } catch (IOException e) {
+ L.e(e, "Could not write to %s", exactPath);
+ }
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/ext/ext.kt b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/ext/ext.kt
new file mode 100644
index 0000000..9d950b1
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/ext/ext.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.ext
+
+import kotlin.properties.ReadOnlyProperty
+import kotlin.properties.Delegates
+import android.databinding.tool.ext.joinToCamelCase
+import android.databinding.tool.ext.joinToCamelCaseAsVar
+import android.databinding.tool.reflection.ModelAnalyzer
+import android.databinding.tool.reflection.ModelClass
+import android.databinding.tool.reflection.ModelAnalyzer
+
+private class LazyExt<K, T>(private val initializer: (k : K) -> T) : ReadOnlyProperty<K, T> {
+ private val mapping = hashMapOf<K, T>()
+ override fun get(thisRef: K, desc: PropertyMetadata): T {
+ val t = mapping.get(thisRef)
+ if (t != null) {
+ return t
+ }
+ val result = initializer(thisRef)
+ mapping.put(thisRef, result)
+ return result
+ }
+}
+
+fun Delegates.lazy<K, T>(initializer: (k : K) -> T): ReadOnlyProperty<K, T> = LazyExt(initializer)
+
+public fun Class<*>.toJavaCode() : String {
+ val name = getName();
+ if (name.startsWith('[')) {
+ val numArray = name.lastIndexOf('[') + 1;
+ val componentType : String;
+ when (name.charAt(numArray)) {
+ 'Z' -> componentType = "boolean"
+ 'B' -> componentType = "byte"
+ 'C' -> componentType = "char"
+ 'L' -> componentType = name.substring(numArray + 1, name.length() - 1).replace('$', '.');
+ 'D' -> componentType = "double"
+ 'F' -> componentType = "float"
+ 'I' -> componentType = "int"
+ 'J' -> componentType = "long"
+ 'S' -> componentType = "short"
+ else -> componentType = name.substring(numArray)
+ }
+ val arrayComp = name.substring(0, numArray).replace("[", "[]");
+ return componentType + arrayComp;
+ } else {
+ return name.replace("$", ".")
+ }
+}
+
+public fun String.androidId() : String = this.split("/")[1]
+
+public fun String.toCamelCase() : String {
+ val split = this.split("_")
+ if (split.size == 0) return ""
+ if (split.size == 1) return split[0].capitalize()
+ return split.joinToCamelCase()
+}
+
+public fun String.toCamelCaseAsVar() : String {
+ val split = this.split("_")
+ if (split.size == 0) return ""
+ if (split.size == 1) return split[0]
+ return split.joinToCamelCaseAsVar()
+}
+
+public fun String.br() : String =
+ "BR.${if (this == "") "_all" else this}"
diff --git a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/ext/list_ext.kt b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/ext/list_ext.kt
new file mode 100644
index 0000000..12c8af2
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/ext/list_ext.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.ext
+
+import android.databinding.tool.ext.toCamelCase
+import android.databinding.tool.ext.toCamelCaseAsVar
+
+public fun List<String>.joinToCamelCase(): String = when(size()) {
+ 0 -> throw IllegalArgumentException("invalid section size, cannot be zero")
+ 1 -> this.get(0).toCamelCase()
+ else -> this.map {it.toCamelCase()}.joinToString("")
+}
+
+public fun List<String>.joinToCamelCaseAsVar(): String = when(size()) {
+ 0 -> throw IllegalArgumentException("invalid section size, cannot be zero")
+ 1 -> this.get(0).toCamelCaseAsVar()
+ else -> get(0).toCamelCaseAsVar() + drop(1).joinToCamelCase()
+}
+
+public fun Array<String>.joinToCamelCase(): String = when(size) {
+ 0 -> throw IllegalArgumentException("invalid section size, cannot be zero")
+ 1 -> this.get(0).toCamelCase()
+ else -> this.map {it.toCamelCase()}.joinToString("")
+}
+
+public fun Array<String>.joinToCamelCaseAsVar(): String = when(size) {
+ 0 -> throw IllegalArgumentException("invalid section size, cannot be zero")
+ 1 -> this.get(0).toCamelCaseAsVar()
+ else -> get(0).toCamelCaseAsVar() + drop(1).joinToCamelCase()
+}
\ No newline at end of file
diff --git a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/ext/node_ext.kt b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/ext/node_ext.kt
new file mode 100644
index 0000000..089e3c8
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/ext/node_ext.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.ext
+
+import org.w3c.dom.Node
+import org.w3c.dom.NodeList
+import java.util.ArrayList
+
+public fun Node.getAndroidId() : String? =
+ getAttributes()?.getNamedItem("android:id")?.getNodeValue()
+
+public fun Node.getAndroidIdPath(includeRoot : Boolean) : List<String> {
+ val ids = arrayListOf<String>()
+ ids.add(getAndroidId()!!)
+ var parent : Node? = getParentNode()
+ while (parent != null && (includeRoot || parent?.getParentNode()?.getParentNode() != null)) {
+ val id = parent?.getAndroidId()
+ if (id != null) {
+ ids.add(id)
+ }
+ parent = parent?.getParentNode()
+ }
+ return ids
+}
+
+public fun NodeList.forEach( f : (Node) -> Unit ) {
+ val cnt = getLength()
+ if (cnt == 0) return
+ for (i in 0..cnt - 1) {
+ f(item(i))
+ }
+}
+public fun NodeList.toArrayList() : ArrayList<Node> {
+ val cnt = getLength()
+ val arrayList = arrayListOf<Node>()
+ for (i in 0..cnt - 1) {
+ arrayList.add(item(i))
+ }
+ return arrayList
+}
+
+
diff --git a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/util/Log.kt b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/util/Log.kt
new file mode 100644
index 0000000..fb6eb35
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/util/Log.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.util
+
+object Log {
+ fun d(s : String) {
+ System.out.println("[debug] $s")
+ }
+
+ fun d(f : () -> String) {
+ System.out.println("[debug] ${f()}")
+ }
+}
\ No newline at end of file
diff --git a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/util/ParserHelper.kt b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/util/ParserHelper.kt
new file mode 100644
index 0000000..6972448
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/util/ParserHelper.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.util
+
+object ParserHelper {
+ public fun toClassName(name:String) : String {
+
+ return stripExtension(name).split("[_-]").map { it.capitalize() }.join("")
+ }
+
+
+ public fun stripExtension(name : String) : String {
+ val dot = name.indexOf(".")
+ return if (dot == -1) name else name.substring(0, name.indexOf("."))
+ }
+}
\ No newline at end of file
diff --git a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/util/XmlEditor.kt b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/util/XmlEditor.kt
new file mode 100644
index 0000000..bea9df5
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/util/XmlEditor.kt
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.util
+
+import java.io.File
+import org.antlr.v4.runtime.ANTLRInputStream
+import java.io.FileReader
+import android.databinding.parser.XMLLexer
+import org.antlr.v4.runtime.CommonTokenStream
+import android.databinding.parser.XMLParser
+import android.databinding.parser.log
+import android.databinding.parser.XMLParserBaseVisitor
+import android.databinding.parser.Position
+import android.databinding.parser.toPosition
+import android.databinding.parser.toEndPosition
+import java.util.Comparator
+import com.google.common.base.Preconditions
+import java.util.ArrayList
+
+/**
+ * Ugly inefficient class to strip unwanted tags from XML.
+ * Band-aid solution to unblock development
+ */
+object XmlEditor {
+ val reservedElementNames = arrayListOf("variable", "import")
+ var rootNodeContext: XMLParser.ElementContext? = null
+ var rootNodeHasTag = false
+ data class LayoutXmlElements(val start: Position, val end: Position,
+ val insertionPoint: Position,
+ val isTag: kotlin.Boolean, val isReserved: kotlin.Boolean,
+ val attributes: MutableList<LayoutXmlElements>?)
+
+ val visitor = object : XMLParserBaseVisitor<MutableList<LayoutXmlElements>>() {
+ override fun visitAttribute(ctx: XMLParser.AttributeContext): MutableList<LayoutXmlElements>? {
+ log { "attr:${ctx.attrName.getText()} ${ctx.attrValue.getText()} . parent: ${ctx.getParent()}" }
+ if (ctx.getParent() == rootNodeContext && ctx.attrName.getText() == "android:tag") {
+ rootNodeHasTag = true
+ }
+ val isTag = ctx.attrName.getText().equals("android:tag")
+ if (isTag || ctx.attrName.getText().startsWith("bind:") ||
+ (ctx.attrValue.getText().startsWith("\"@{") && ctx.attrValue.getText().endsWith("}\"")) ||
+ (ctx.attrValue.getText().startsWith("'@{") && ctx.attrValue.getText().endsWith("}'"))) {
+ return arrayListOf(LayoutXmlElements(ctx.getStart().toPosition(),
+ ctx.getStop().toEndPosition(), ctx.getStart().toPosition(), isTag, false, null))
+ }
+
+ //log{"visiting attr: ${ctx.getText()} at location ${ctx.getStart().toS()} ${ctx.getStop().toS()}"}
+ return super<XMLParserBaseVisitor>.visitAttribute(ctx)
+ }
+
+ override fun visitElement(ctx: XMLParser.ElementContext): MutableList<LayoutXmlElements>? {
+ log { "elm ${ctx.elmName.getText()} || ${ctx.Name()} paren : ${ctx.getParent()}" }
+ if (rootNodeContext == null) {
+ rootNodeContext = ctx
+ }
+ val insertionPoint : Position
+ if (ctx.content() == null) {
+ insertionPoint = ctx.getStop().toPosition()
+ insertionPoint.charIndex;
+ } else {
+ insertionPoint = ctx.content().getStart().toPosition()
+ insertionPoint.charIndex -= 2;
+ }
+ if (reservedElementNames.contains(ctx.elmName?.getText()) || ctx.elmName.getText().startsWith("bind:")) {
+ return arrayListOf(LayoutXmlElements(ctx.getStart().toPosition(),
+ ctx.getStop().toEndPosition(), insertionPoint, false, true, arrayListOf()));
+ }
+ val elements = super<XMLParserBaseVisitor>.visitElement(ctx);
+ if (elements != null && !elements.isEmpty()) {
+ val attributes : MutableList<LayoutXmlElements> = arrayListOf();
+ val others : MutableList<LayoutXmlElements> = arrayListOf();
+ elements.forEach {
+ if (it.attributes == null) {
+ attributes.add(it);
+ } else {
+ others.add(it);
+ }
+ }
+ if (attributes.isEmpty()) {
+ return elements;
+ } else {
+ val element = LayoutXmlElements(ctx.getStart().toPosition(),
+ ctx.getStop().toEndPosition(), insertionPoint, false, false, attributes)
+ others.add(0, element);
+ return others;
+ }
+ } else {
+ return elements;
+ }
+ }
+
+ override fun defaultResult(): MutableList<LayoutXmlElements>? = arrayListOf()
+
+ override fun aggregateResult(aggregate: MutableList<LayoutXmlElements>?, nextResult: MutableList<LayoutXmlElements>?): MutableList<LayoutXmlElements>? =
+ if (aggregate == null) {
+ nextResult
+ } else if (nextResult == null) {
+ aggregate
+ } else {
+ aggregate.addAll(nextResult)
+ aggregate
+ }
+ }
+
+ fun strip(f: File, newTag: String? = null): String? {
+ rootNodeContext = null //clear it
+ rootNodeHasTag = false
+ val inputStream = ANTLRInputStream(FileReader(f))
+ val lexer = XMLLexer(inputStream)
+ val tokenStream = CommonTokenStream(lexer)
+ val parser = XMLParser(tokenStream)
+ val expr = parser.document()
+ val parsedExpr = expr.accept(visitor)
+ if (parsedExpr.isEmpty()) {
+ return null//nothing to strip
+ }
+ Preconditions.checkNotNull(rootNodeContext, "Cannot find root node for ${f.getName()}")
+ Preconditions.checkState(rootNodeHasTag == false, """You cannot set a tag in the layout
+ root if you are using binding. Invalid file: ${f}""")
+ val rootNodeBounds = Pair(rootNodeContext!!.getStart().toPosition(),
+ rootNodeContext!!.getStop().toEndPosition())
+
+ log { "root node bounds: ${rootNodeBounds}" }
+ val out = StringBuilder()
+ val lines = f.readLines("utf-8")
+
+ lines.forEach { out.appendln(it) }
+
+ // TODO we probably don't need to sort
+ val sorted = parsedExpr.sortBy(object : Comparator<LayoutXmlElements> {
+ override fun compare(o1: LayoutXmlElements, o2: LayoutXmlElements): Int {
+ val lineCmp = o1.start.line.compareTo(o2.start.charIndex)
+ if (lineCmp != 0) {
+ return lineCmp
+ }
+ return o1.start.line.compareTo(o2.start.charIndex)
+ }
+ })
+ var lineStarts = arrayListOf(0)
+ lines.withIndex().forEach {
+ if (it.index > 0) {
+ lineStarts.add(lineStarts[it.index - 1] + lines[it.index - 1].length() + 1)
+ }
+ }
+
+ val separator = System.lineSeparator().charAt(0)
+ val noTag : ArrayList<Pair<String, LayoutXmlElements>> = ArrayList()
+ var bindingIndex = 0
+ val rootNodeEnd = toIndex(lineStarts, rootNodeContext!!.content().getStart().toPosition())
+ sorted.forEach {
+ if (it.isReserved) {
+ log {"Replacing reserved tag at ${it.start} to ${it.end}"}
+ replace(out, lineStarts, it, "", separator);
+ } else if (it.attributes != null) {
+ log {"Found attribute for tag at ${it.start} to ${it.end}"}
+ if (it.attributes.size() == 1 && it.attributes.get(0).isTag) {
+ log {"only android:tag"}
+ // no binding, just tag -- don't replace anything
+ } else {
+ var replaced = false
+ val tag : String
+ if (toIndex(lineStarts, it.start) < rootNodeEnd) {
+ tag = ""
+ } else {
+ val index = bindingIndex++;
+ tag = "android:tag=\"bindingTag${index}\"";
+ }
+ it.attributes.forEach {
+ if (!replaced && tagWillFit(it.start, it.end, tag)) {
+ replace(out, lineStarts, it, tag, separator)
+ replaced = true;
+ } else {
+ replace(out, lineStarts, it, "", separator)
+ }
+ }
+ if (!replaced && !tag.isEmpty()) {
+ log {"Could not find place for ${tag}"}
+ noTag.add(0, Pair(tag, it))
+ }
+ }
+ }
+ }
+
+ noTag.forEach {
+ val element = it.second
+ val tag = it.first;
+
+ val insertionPoint = toIndex(lineStarts, element.insertionPoint)
+ out.insert(insertionPoint, " ${tag}")
+ }
+
+ Log.d{"new tag to set: $newTag"}
+ if (newTag != null) {
+ Preconditions.checkState(rootNodeBounds.first.line != rootNodeBounds.second.line,
+ """The root tag should be multi line to add the tag. ${f.getName()}""")
+ val line = rootNodeBounds.first.line
+ out.insert(lineStarts[line] + lines[line].length(), """ android:tag = "$newTag" """)
+ }
+
+
+ return out.toString()
+ }
+
+ fun tagWillFit(start: Position, end: Position, tag: String) : kotlin.Boolean {
+ if (start.line != end.line) {
+ return end.charIndex >= tag.length();
+ }
+ return (end.charIndex - start.charIndex >= tag.length());
+ }
+
+ fun replace(out : StringBuilder, lineStarts : ArrayList<kotlin.Int>, element : LayoutXmlElements,
+ tag : String, separator : kotlin.Char) {
+ val posStart = toIndex(lineStarts, element.start)
+ val posEnd = toIndex(lineStarts, element.end)
+ log {"replacing '${out.substring(posStart, posEnd)}' with '${tag}'"}
+ val spaceEnd = posEnd - tag.length();
+ for ( i in posStart..(spaceEnd - 1)) {
+ if (out.charAt(i) != separator) {
+ out.setCharAt(i, ' ')
+ }
+ }
+ out.replace(spaceEnd, posEnd, tag);
+ }
+
+ fun toIndex(lineStarts : ArrayList<kotlin.Int>, pos : Position) : kotlin.Int {
+ return lineStarts[pos.line] + pos.charIndex;
+ }
+}
diff --git a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/DataBinderWriter.kt b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/DataBinderWriter.kt
new file mode 100644
index 0000000..f7afc93
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/DataBinderWriter.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.writer
+
+import android.databinding.tool.LayoutBinder
+
+class DataBinderWriter(val pkg: String, val projectPackage: String, val className: String,
+ val layoutBinders : List<LayoutBinder>, val minSdk : kotlin.Int) {
+ fun write() =
+ kcode("") {
+ nl("package $pkg;")
+ nl("import $projectPackage.BR;")
+ nl("class $className {") {
+ tab("public android.databinding.ViewDataBinding getDataBinder(android.view.View view, int layoutId) {") {
+ tab("switch(layoutId) {") {
+ layoutBinders.groupBy{it.getLayoutname()}.forEach {
+ tab("case ${it.value.first!!.getModulePackage()}.R.layout.${it.value.first!!.getLayoutname()}:") {
+ if (it.value.size() == 1) {
+ tab("return ${it.value.first!!.getPackage()}.${it.value.first!!.getImplementationName()}.bind(view);")
+ } else {
+ // we should check the tag to decide which layout we need to inflate
+ tab("{") {
+ tab("final Object tag = view.getTag();")
+ tab("if(tag == null) throw new java.lang.RuntimeException(\"view must have a tag\");")
+ it.value.forEach {
+ tab("if (\"${it.getTag()}\".equals(tag)) {") {
+ tab("return new ${it.getPackage()}.${it.getImplementationName()}(view);")
+ } tab("}")
+ }
+ }tab("}")
+ }
+
+ }
+ }
+ }
+ tab("}")
+ tab("return null;")
+ }
+ tab("}")
+
+ tab("public int getId(String key) {") {
+ tab("return BR.getId(key);")
+ } tab("}")
+
+ tab("final static int TARGET_MIN_SDK = ${minSdk};")
+ }
+ nl("}")
+ }.generate()
+}
\ No newline at end of file
diff --git a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/KCode.kt b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/KCode.kt
new file mode 100644
index 0000000..f09d018
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/KCode.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.writer
+
+import java.util.BitSet
+
+class KCode (private val s : String? = null){
+
+ private var sameLine = false
+
+ private val lineSeparator = System.getProperty("line.separator")
+
+ class Appendix(val glue : String, val code : KCode)
+
+ private val nodes = arrayListOf<Any>()
+
+ class object {
+ private val cachedIndentations = BitSet()
+ private val indentCache = arrayListOf<String>()
+ fun indent(n: Int): String {
+ if (cachedIndentations.get(n)) {
+ return indentCache.get(n)
+ }
+ val s = (0..n-1).fold(""){prev, next -> "${prev} "}
+ cachedIndentations.set(n, true )
+ while (indentCache.size() <= n) {
+ indentCache.add("");
+ }
+ indentCache.set(n, s)
+ return s
+ }
+ }
+
+ fun isNull(kcode : KCode?) = kcode == null || (kcode.nodes.isEmpty() && (kcode.s == null || kcode.s.trim() == ""))
+
+ fun tab(vararg codes : KCode?) : KCode {
+ codes.forEach { tab(it) }
+ return this
+ }
+
+ fun tab(codes : Collection<KCode?> ) : KCode {
+ codes.forEach { tab(it) }
+ return this
+ }
+
+ fun tab(s : String?, init : (KCode.() -> Unit)? = null) : KCode {
+ val c = KCode(s)
+ if (init != null) {
+ c.init()
+ }
+ return tab(c)
+ }
+
+ private fun tab(c : KCode?) : KCode {
+ if (isNull(c)) {
+ return this
+ }
+ nodes.add(c)
+ return this
+ }
+
+ fun nls(vararg codes : KCode?) : KCode {
+ codes.forEach { nl(it) }
+ return this
+ }
+
+ fun nls(codes : Collection<KCode?>) : KCode {
+ codes.forEach { nl(it) }
+ return this
+ }
+
+ fun nl(c : KCode?) : KCode {
+ if (isNull(c)) {
+ return this
+ }
+ nodes.add(c)
+ c!!.sameLine = true
+ return this
+ }
+
+ fun nl(s : String?, init : (KCode.() -> Unit)? = null) : KCode {
+ val c = KCode(s)
+ if (init != null) {
+ c.init()
+ }
+ return nl(c)
+ }
+
+ fun apps(glue : String = "", vararg codes : KCode?) : KCode {
+ codes.forEach { app(glue, it)}
+ return this
+ }
+
+ fun apps(glue : String = "", codes : Collection<KCode?>) : KCode {
+ codes.forEach { app(glue, it)}
+ return this
+ }
+
+ fun app(glue : String = "", c : KCode?) : KCode {
+ if (isNull(c)) {
+ return this
+ }
+ nodes.add(Appendix(glue, c!!))
+ return this
+ }
+
+ fun app(s : String) : KCode {
+ val c = KCode(s)
+ return app("", c)
+ }
+
+ fun app(glue : String = "", s : String?, init : (KCode.() -> Unit)? = null) : KCode {
+ val c = KCode(s)
+ if (init != null) {
+ c.init()
+ }
+ return app(glue, c)
+ }
+
+
+ fun toS(n : Int, sb : StringBuilder) {
+ if (s != null) {
+ sb.append(s)
+ }
+ val newlineFirstNode = s != null || (nodes.isNotEmpty() && nodes.first() is Appendix)
+ var addedChild = false
+ nodes.forEach { when(it) {
+ is Appendix -> {
+ sb.append(it.glue)
+ it.code.toS(n, sb)
+ }
+ is KCode -> {
+ val childTab = n + (if(it.sameLine) 0 else 1)
+ if (addedChild || newlineFirstNode) {
+ sb.append(lineSeparator)
+ sb.append("${indent(childTab)}")
+ }
+ it.toS(childTab, sb)
+ addedChild = true
+ }
+ } }
+
+ }
+
+ fun generate() : String {
+ val sb = StringBuilder()
+ toS(0, sb)
+ return sb.toString()
+ }
+}
+
+fun kcode(s : String?, init : (KCode.() -> Unit)? = null) : KCode {
+ val c = KCode(s)
+ if (init != null) {
+ c.init()
+ }
+ return c
+}
\ No newline at end of file
diff --git a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt
new file mode 100644
index 0000000..61f0d2d
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt
@@ -0,0 +1,878 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.writer
+
+import android.databinding.tool.LayoutBinder
+import android.databinding.tool.expr.Expr
+import kotlin.properties.Delegates
+import android.databinding.tool.ext.joinToCamelCaseAsVar
+import android.databinding.tool.BindingTarget
+import android.databinding.tool.expr.IdentifierExpr
+import android.databinding.tool.util.Log
+import java.util.BitSet
+import android.databinding.tool.expr.ExprModel
+import java.util.Arrays
+import android.databinding.tool.expr.TernaryExpr
+import android.databinding.tool.expr.FieldAccessExpr
+import android.databinding.tool.expr.ComparisonExpr
+import android.databinding.tool.expr.GroupExpr
+import android.databinding.tool.expr.MathExpr
+import android.databinding.tool.expr.MethodCallExpr
+import android.databinding.tool.expr.StaticIdentifierExpr
+import android.databinding.tool.expr.SymbolExpr
+import android.databinding.tool.ext.androidId
+import android.databinding.tool.ext.lazy
+import android.databinding.tool.ext.br
+import android.databinding.tool.expr.ResourceExpr
+import android.databinding.tool.expr.BracketExpr
+import android.databinding.tool.reflection.Callable
+import android.databinding.tool.expr.CastExpr
+import android.databinding.tool.reflection.ModelAnalyzer
+import java.util.HashMap
+
+fun String.stripNonJava() = this.split("[^a-zA-Z0-9]").map{ it.trim() }.joinToCamelCaseAsVar()
+
+class ExprModelExt {
+ val usedFieldNames = hashSetOf<String>()
+ val localizedFlags = arrayListOf<FlagSet>()
+
+ fun localizeFlag(set : FlagSet, name:String) : FlagSet {
+ localizedFlags.add(set)
+ val result = getUniqueFieldName(name)
+ set.setLocalName(result)
+ return set
+ }
+
+ fun getUniqueFieldName(base : String) : String {
+ var candidate = base
+ var i = 0
+ while (usedFieldNames.contains(candidate)) {
+ i ++
+ candidate = base + i
+ }
+ usedFieldNames.add(candidate)
+ return candidate
+ }
+}
+
+val ExprModel.ext by Delegates.lazy { target : ExprModel ->
+ ExprModelExt()
+}
+
+fun ExprModel.getUniqueFieldName(base : String) : String = ext.getUniqueFieldName(base)
+
+fun ExprModel.localizeFlag(set : FlagSet, base : String) : FlagSet = ext.localizeFlag(set, base)
+
+val BindingTarget.readableUniqueName by Delegates.lazy { target: BindingTarget ->
+ val variableName : String
+ if (target.getId() == null) {
+ variableName = "boundView" + target.getTag()
+ } else {
+ variableName = target.getId().androidId().stripNonJava()
+ }
+ target.getModel().ext.getUniqueFieldName(variableName)
+}
+
+fun BindingTarget.superConversion(variable : String) : String {
+ if (isBinder()) {
+ return "${getViewClass()}.bind(${variable})"
+ } else if (getResolvedType() != null && getResolvedType().extendsViewStub()) {
+ return "new android.databinding.ViewStubProxy((android.view.ViewStub) ${variable})"
+ } else {
+ return "(${interfaceType}) ${variable}"
+ }
+}
+
+val BindingTarget.fieldName by Delegates.lazy { target : BindingTarget ->
+ if (target.getFieldName() == null) {
+ if (target.getId() == null) {
+ target.setFieldName("m${target.readableUniqueName.capitalize()}")
+ } else {
+ target.androidId.stripNonJava();
+ target.setFieldName(target.readableUniqueName);
+ }
+ }
+ target.getFieldName();
+}
+
+val BindingTarget.getterName by Delegates.lazy { target : BindingTarget ->
+ "get${target.readableUniqueName.capitalize()}"
+}
+
+val BindingTarget.androidId by Delegates.lazy { target : BindingTarget ->
+ "R.id.${target.getId().androidId()}"
+}
+
+val BindingTarget.interfaceType by Delegates.lazy { target : BindingTarget ->
+ if (target.getResolvedType() != null && target.getResolvedType().extendsViewStub()) {
+ "android.databinding.ViewStubProxy"
+ } else {
+ target.getInterfaceType()
+ }
+}
+
+val Expr.readableUniqueName by Delegates.lazy { expr : Expr ->
+ Log.d { "readableUniqueName for ${expr.getUniqueKey()}" }
+ val stripped = "${expr.getUniqueKey().stripNonJava()}"
+ expr.getModel().ext.getUniqueFieldName(stripped)
+}
+
+val Expr.readableName by Delegates.lazy { expr : Expr ->
+ Log.d { "readableUniqueName for ${expr.getUniqueKey()}" }
+ "${expr.getUniqueKey().stripNonJava()}"
+}
+
+val Expr.fieldName by Delegates.lazy { expr : Expr ->
+ "m${expr.readableName.capitalize()}"
+}
+
+val Expr.hasFlag by Delegates.lazy { expr : Expr ->
+ expr.getId() < expr.getModel().getInvalidateableFieldLimit()
+}
+
+val Expr.localName by Delegates.lazy { expr : Expr ->
+ if(expr.isVariable()) expr.fieldName else "${expr.readableUniqueName}"
+}
+
+val Expr.setterName by Delegates.lazy { expr : Expr ->
+ "set${expr.readableName.capitalize()}"
+}
+
+val Expr.onChangeName by Delegates.lazy { expr : Expr ->
+ "onChange${expr.readableUniqueName.capitalize()}"
+}
+
+val Expr.getterName by Delegates.lazy { expr : Expr ->
+ "get${expr.readableName.capitalize()}"
+}
+
+val Expr.dirtyFlagName by Delegates.lazy { expr : Expr ->
+ "sFlag${expr.readableUniqueName.capitalize()}"
+}
+
+val Expr.shouldReadFlagName by Delegates.lazy { expr : Expr ->
+ "sFlagRead${expr.readableUniqueName.capitalize()}"
+}
+
+val Expr.invalidateFlagName by Delegates.lazy { expr : Expr ->
+ "sFlag${expr.readableUniqueName.capitalize()}Invalid"
+}
+
+val Expr.conditionalFlagPrefix by Delegates.lazy { expr : Expr ->
+ "sFlag${expr.readableUniqueName.capitalize()}Is"
+}
+
+
+fun Expr.toCode(full : Boolean = false) : KCode {
+ val it = this
+ if (isDynamic() && !full) {
+ return kcode(localName)
+ }
+ return when (it) {
+ is ComparisonExpr -> kcode("") {
+ app("", it.getLeft().toCode())
+ app(it.getOp())
+ app("", it.getRight().toCode())
+ }
+ is FieldAccessExpr -> kcode("") {
+ app("", it.getChild().toCode())
+ if (it.getGetter().type == Callable.Type.FIELD) {
+ app(".", it.getGetter().name)
+ } else {
+ app(".", it.getGetter().name).app("()")
+ }
+ }
+ is GroupExpr -> kcode("(").app("", it.getWrapped().toCode()).app(")")
+ is StaticIdentifierExpr -> kcode(it.getResolvedType().toJavaCode())
+ is IdentifierExpr -> kcode(it.localName)
+ is MathExpr -> kcode("") {
+ app("", it.getLeft().toCode())
+ app(it.getOp())
+ app("", it.getRight().toCode())
+ }
+ is MethodCallExpr -> kcode("") {
+ app("", it.getTarget().toCode())
+ app(".", it.getGetter().name)
+ app("(")
+ var first = true
+ it.getArgs().forEach {
+ apps(if (first) "" else ",", it.toCode())
+ first = false
+ }
+ app(")")
+ }
+ is SymbolExpr -> kcode(it.getText()) // TODO
+ is TernaryExpr -> kcode("") {
+ app("", it.getPred().toCode())
+ app("?", it.getIfTrue().toCode())
+ app(":", it.getIfFalse().toCode())
+ }
+ is ResourceExpr -> kcode("") {
+ app("", it.toJava())
+ }
+ is BracketExpr -> kcode("") {
+ app("", it.getTarget().toCode())
+ val bracketType = it.getAccessor();
+ when (bracketType) {
+ BracketExpr.BracketAccessor.ARRAY -> {
+ app("[", it.getArg().toCode())
+ app("]")
+ }
+ BracketExpr.BracketAccessor.LIST -> {
+ app(".get(")
+ if (it.argCastsInteger()) {
+ app("(Integer)")
+ }
+ app("", it.getArg().toCode())
+ app(")")
+ }
+ BracketExpr.BracketAccessor.MAP -> {
+ app(".get(", it.getArg().toCode())
+ app(")")
+ }
+ }
+ }
+ is CastExpr -> kcode("") {
+ app("(", it.getCastType())
+ app(") ", it.getCastExpr().toCode())
+ }
+ else -> kcode("//NOT IMPLEMENTED YET")
+ }
+
+}
+
+fun Expr.isVariable() = this is IdentifierExpr && this.isDynamic()
+
+fun Expr.conditionalFlagName(output : Boolean, suffix : String) = "${dirtyFlagName}_${output}$suffix"
+
+
+val Expr.dirtyFlagSet by Delegates.lazy { expr : Expr ->
+ FlagSet(expr.getInvalidFlags(), expr.getModel().getFlagBucketCount())
+}
+
+val Expr.invalidateFlagSet by Delegates.lazy { expr : Expr ->
+ FlagSet(expr.getId())
+}
+
+val Expr.shouldReadFlagSet by Delegates.lazy { expr : Expr ->
+ FlagSet(expr.getShouldReadFlags(), expr.getModel().getFlagBucketCount())
+}
+
+val Expr.conditionalFlags by Delegates.lazy { expr : Expr ->
+ arrayListOf(FlagSet(expr.getRequirementFlagIndex(false)),
+ FlagSet(expr.getRequirementFlagIndex(true)))
+}
+
+fun Expr.getRequirementFlagSet(expected : Boolean) : FlagSet = conditionalFlags[if(expected) 1 else 0]
+
+fun FlagSet.notEmpty(cb : (suffix : String, value : Long) -> Unit) {
+ buckets.withIndex().forEach {
+ if (it.value != 0L) {
+ cb(getWordSuffix(it.index), buckets[it.index])
+ }
+ }
+}
+
+fun FlagSet.getBitSuffix(bitIndex : Int) : String {
+ val word = bitIndex / FlagSet.sBucketSize
+ return getWordSuffix(word)
+}
+
+fun FlagSet.getWordSuffix(wordIndex : Int) : String {
+ return if(wordIndex == 0) "" else "_${wordIndex}"
+}
+
+fun FlagSet.localValue(bucketIndex : Int) =
+ if (getLocalName() == null) binaryCode(bucketIndex)
+ else "${getLocalName()}${getWordSuffix(bucketIndex)}"
+
+fun FlagSet.binaryCode(bucketIndex : Int) = longToBinary(buckets[bucketIndex])
+
+
+fun longToBinary(l : Long) =
+ "0b${java.lang.Long.toBinaryString(l)}L"
+
+fun <T> FlagSet.mapOr(other : FlagSet, cb : (suffix : String, index : Int) -> T) : List<T> {
+ val min = Math.min(buckets.size(), other.buckets.size())
+ val result = arrayListOf<T>()
+ for (i in 0..(min - 1)) {
+ // if these two can match by any chance, call the callback
+ if (intersect(other, i)) {
+ result.add(cb(getWordSuffix(i), i))
+ }
+ }
+ return result
+}
+
+class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
+ val model = layoutBinder.getModel()
+ val indices = HashMap<BindingTarget, kotlin.Int>()
+ val mDirtyFlags by Delegates.lazy {
+ val fs = FlagSet(BitSet(), model.getFlagBucketCount());
+ Arrays.fill(fs.buckets, -1)
+ fs.setDynamic(true)
+ model.localizeFlag(fs, "mDirtyFlags")
+ fs
+ }
+
+ val dynamics by Delegates.lazy { model.getExprMap().values().filter { it.isDynamic() } }
+ val className = layoutBinder.getImplementationName()
+
+ val identifiers by Delegates.lazy {
+ dynamics.filter { it is IdentifierExpr }
+ }
+
+ val baseClassName = "${layoutBinder.getClassName()}"
+
+ val includedBinders by Delegates.lazy {
+ layoutBinder.getBindingTargets().filter { it.isBinder() }
+ }
+
+ val variables by Delegates.lazy {
+ model.getExprMap().values().filterIsInstance(javaClass<IdentifierExpr>()).filter { it.isVariable() }
+ }
+
+ val usedVariables by Delegates.lazy {
+ variables.filter {it.isUsed()}
+ }
+
+ public fun write() : String {
+ layoutBinder.resolveWhichExpressionsAreUsed()
+ calculateIndices();
+ return kcode("package ${layoutBinder.getPackage()};") {
+ nl("import ${layoutBinder.getModulePackage()}.R;")
+ nl("import ${layoutBinder.getModulePackage()}.BR;")
+ nl("import android.view.View;")
+ val classDeclaration : String
+ if (layoutBinder.hasVariations()) {
+ classDeclaration = "${className} extends ${baseClassName}"
+ } else {
+ classDeclaration = "${className} extends android.databinding.ViewDataBinding"
+ }
+ nl("public class ${classDeclaration} {") {
+ tab(declareIncludeViews())
+ tab(declareViews())
+ tab(declareVariables())
+ tab(declareConstructor())
+ tab(declareInvalidateAll())
+ tab(declareLog())
+ tab(declareSetVariable())
+ tab(variableSettersAndGetters())
+ tab(onFieldChange())
+
+ tab(executePendingBindings())
+
+ tab(declareDirtyFlags())
+ if (!layoutBinder.hasVariations()) {
+ tab(declareFactories())
+ }
+ }
+ nl("}")
+ tab(flagMapping())
+ tab("//end")
+ }.generate()
+ }
+ fun calculateIndices() : Unit {
+ val numTaggedViews = layoutBinder.getBindingTargets().
+ filter{it.isUsed() && it.getTag() != null}.count()
+ layoutBinder.getBindingTargets().filter{ it.isUsed() && it.getTag() != null }.forEach {
+ indices.put(it, Integer.parseInt(it.getTag()));
+ }
+ layoutBinder.getBindingTargets().filter{ it.isUsed() && it.getTag() == null && it.getId() != null }.withIndex().forEach {
+ indices.put(it.value, it.index + numTaggedViews);
+ }
+ }
+ fun declareIncludeViews() = kcode("") {
+ nl("private static final android.util.SparseIntArray sIncludes;")
+ nl("private static final android.util.SparseIntArray sViewsWithIds;")
+ nl("static {") {
+ val hasBinders = layoutBinder.getBindingTargets().firstOrNull{ it.isUsed() && it.isBinder()} != null
+ if (!hasBinders) {
+ tab("sIncludes = null;")
+ } else {
+ tab("sIncludes = new android.util.SparseIntArray();")
+ layoutBinder.getBindingTargets().filter{ it.isUsed() && it.isBinder()}.forEach {
+ tab("sIncludes.put(${it.androidId}, ${indices.get(it)});")
+ }
+ }
+ val hasViewsWithIds = layoutBinder.getBindingTargets().firstOrNull{
+ it.isUsed() && (!it.supportsTag() || (it.getId() != null && it.getTag() == null))
+ } != null
+ if (!hasViewsWithIds) {
+ tab("sViewsWithIds = null;")
+ } else {
+ tab("sViewsWithIds = new android.util.SparseIntArray();")
+ layoutBinder.getBindingTargets().filter{
+ it.isUsed() && (!it.supportsTag() || (it.getId() != null && it.getTag() == null))
+ }.forEach {
+ tab("sViewsWithIds.put(${it.androidId}, ${indices.get(it)});")
+ }
+ }
+ }
+ nl("}")
+ }
+ fun declareConstructor() = kcode("") {
+ val viewCount = layoutBinder.getBindingTargets().filter{it.isUsed()}.count()
+ if (layoutBinder.hasVariations()) {
+ nl("")
+ nl("public ${className}(View root) {") {
+ tab("this(root, mapChildViews(root, ${viewCount}, sIncludes, sViewsWithIds));")
+ }
+ nl("}")
+ nl("private ${className}(View root, View[] views) {") {
+ tab("super(root, ${model.getObservables().size()}") {
+ layoutBinder.getBindingTargets().filter { it.getId() != null }.forEach {
+ tab(", ${fieldConversion(it)}")
+ }
+ tab(");")
+ }
+ }
+ } else {
+ nl("${baseClassName}(View root) {") {
+ tab("super(root, ${model.getObservables().size()});")
+ tab("final View[] views = mapChildViews(root, ${viewCount}, sIncludes, sViewsWithIds);")
+ }
+ }
+ val taggedViews = layoutBinder.getBindingTargets().filter{it.isUsed()}
+ taggedViews.forEach {
+ if (!layoutBinder.hasVariations() || it.getId() == null) {
+ tab("this.${it.fieldName} = ${fieldConversion(it)};")
+ }
+ if (!it.isBinder()) {
+ if (it.getResolvedType() != null && it.getResolvedType().extendsViewStub()) {
+ tab("this.${it.fieldName}.setContainingBinding(this);")
+ }
+ if (it.supportsTag() && it.getTag() != null) {
+ val originalTag = it.getOriginalTag();
+ var tagValue = "null"
+ if (originalTag != null) {
+ tagValue = "\"${originalTag}\""
+ if (originalTag.startsWith("@")) {
+ var packageName = layoutBinder.getModulePackage()
+ if (originalTag.startsWith("@android:")) {
+ packageName = "android"
+ }
+ val slashIndex = originalTag.indexOf('/')
+ val resourceId = originalTag.substring(slashIndex + 1)
+ tagValue = "root.getResources().getString(${packageName}.R.string.${resourceId})"
+ }
+ }
+ tab("this.${it.fieldName}.setTag(${tagValue});")
+ }
+ }
+ }
+ tab("invalidateAll();");
+ nl("}")
+ }
+
+ fun fieldConversion(target : BindingTarget) : String {
+ val index = indices.get(target)
+ if (!target.isUsed()) {
+ return "null"
+ } else {
+ val variableName: String
+ if (index == null) {
+ variableName = "root";
+ } else {
+ variableName = "views[${index}]"
+ }
+ return target.superConversion(variableName)
+ }
+ }
+
+ fun declareInvalidateAll() = kcode("") {
+ nl("@Override")
+ nl("public void invalidateAll() {") {
+ val bs = BitSet()
+ bs.set(0, model.getInvalidateableFieldLimit())
+ val fs = FlagSet(bs, mDirtyFlags.buckets.size())
+ for (i in (0..(mDirtyFlags.buckets.size() - 1))) {
+ tab("${mDirtyFlags.localValue(i)} = ${fs.localValue(i)};")
+ }
+ includedBinders.filter{it.isUsed()}.forEach { binder ->
+ tab("${binder.fieldName}.invalidateAll();")
+ }
+ }
+ nl("}")
+ }
+
+ fun declareSetVariable() = kcode("") {
+ nl("public boolean setVariable(int variableId, Object variable) {") {
+ tab("switch(variableId) {") {
+ usedVariables.forEach {
+ tab ("case ${it.getName().br()} :") {
+ tab("${it.setterName}((${it.getResolvedType().toJavaCode()}) variable);")
+ tab("return true;")
+ }
+ }
+ }
+ tab("}")
+ tab("return false;")
+ }
+ nl("}")
+ }
+
+ fun declareLog() = kcode("") {
+ nl("private void log(String msg, long i) {") {
+ tab("""android.util.Log.d("BINDER", msg + ":" + Long.toHexString(i));""")
+ }
+ nl("}")
+ }
+
+ fun variableSettersAndGetters() = kcode("") {
+ variables.filterNot{it.isUsed()}.forEach {
+ nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableUniqueName}) {") {
+ tab("// not used, ignore")
+ }
+ nl("}")
+ nl("")
+ nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
+ tab("return ${it.getDefaultValue()};")
+ }
+ nl("}")
+ }
+ usedVariables.forEach {
+ if (it.getUserDefinedType() != null) {
+ nl("public void ${it.setterName}(${it.getResolvedType().toJavaCode()} ${it.readableUniqueName}) {") {
+ if (it.isObservable()) {
+ tab("updateRegistration(${it.getId()}, ${it.readableUniqueName});");
+ }
+ tab("this.${it.fieldName} = ${it.readableUniqueName};")
+ // set dirty flags!
+ val flagSet = it.invalidateFlagSet
+ mDirtyFlags.mapOr(flagSet) { suffix, index ->
+ tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
+ }
+ tab("super.requestRebind();")
+ }
+ nl("}")
+ nl("")
+ nl("public ${it.getResolvedType().toJavaCode()} ${it.getterName}() {") {
+ tab("return ${it.fieldName};")
+ }
+ nl("}")
+ }
+ }
+ }
+
+ fun onFieldChange() = kcode("") {
+ nl("@Override")
+ nl("protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {") {
+ tab("switch (localFieldId) {") {
+ model.getObservables().forEach {
+ tab("case ${it.getId()} :") {
+ tab("return ${it.onChangeName}((${it.getResolvedType().toJavaCode()}) object, fieldId);")
+ }
+ }
+ }
+ tab("}")
+ tab("return false;")
+ }
+ nl("}")
+ nl("")
+
+ model.getObservables().forEach {
+ nl("private boolean ${it.onChangeName}(${it.getResolvedType().toJavaCode()} ${it.readableUniqueName}, int fieldId) {") {
+ tab("switch (fieldId) {", {
+ val accessedFields: List<FieldAccessExpr> = it.getParents().filterIsInstance(javaClass<FieldAccessExpr>())
+ accessedFields.filter { it.canBeInvalidated() }
+ .groupBy { it.getName() }
+ .forEach {
+ tab("case ${it.key.br()}:") {
+ val field = it.value.first()
+ mDirtyFlags.mapOr(field.invalidateFlagSet) { suffix, index ->
+ tab("${mDirtyFlags.localValue(index)} |= ${field.invalidateFlagSet.localValue(index)};")
+ }
+ tab("return true;")
+ }
+
+ }
+ tab("case ${"".br()}:") {
+ val flagSet = it.invalidateFlagSet
+ mDirtyFlags.mapOr(flagSet) { suffix, index ->
+ tab("${mDirtyFlags.getLocalName()}$suffix |= ${flagSet.localValue(index)};")
+ }
+ tab("return true;")
+ }
+
+ })
+ tab("}")
+ tab("return false;")
+ }
+ nl("}")
+ nl("")
+ }
+ }
+
+ fun declareViews() = kcode("// views") {
+ val oneLayout = !layoutBinder.hasVariations();
+ layoutBinder.getBindingTargets().filter {it.isUsed() && (oneLayout || it.getId() == null)}.forEach {
+ val access : String
+ if (oneLayout && it.getId() != null) {
+ access = "public"
+ } else {
+ access = "private"
+ }
+ nl("${access} final ${it.interfaceType} ${it.fieldName};")
+ }
+ }
+
+ fun declareVariables() = kcode("// variables") {
+ usedVariables.forEach {
+ nl("private ${it.getResolvedType().toJavaCode()} ${it.fieldName};")
+ }
+ }
+
+ fun declareDirtyFlags() = kcode("// dirty flag") {
+ model.ext.localizedFlags.forEach { flag ->
+ flag.notEmpty { suffix, value ->
+ nl("private")
+ app(" ", if(flag.isDynamic()) null else "static final");
+ app(" ", " ${flag.type} ${flag.getLocalName()}$suffix = ${longToBinary(value)};")
+ }
+ }
+ }
+
+ fun flagMapping() = kcode("/* flag mapping") {
+ if (model.getFlagMapping() != null) {
+ val mapping = model.getFlagMapping()
+ for (i in mapping.indices) {
+ tab("flag $i: ${mapping[i]}")
+ }
+ }
+ nl("flag mapping end*/")
+ }
+
+ fun executePendingBindings() = kcode("") {
+ nl("@Override")
+ nl("public void executePendingBindings() {") {
+ val tmpDirtyFlags = FlagSet(mDirtyFlags.buckets)
+ tmpDirtyFlags.setLocalName("dirtyFlags");
+ for (i in (0..mDirtyFlags.buckets.size() - 1)) {
+ tab("${tmpDirtyFlags.type} ${tmpDirtyFlags.localValue(i)} = ${mDirtyFlags.localValue(i)};")
+ tab("${mDirtyFlags.localValue(i)} = 0;")
+ }
+ model.getPendingExpressions().filterNot {it.isVariable()}.forEach {
+ tab("${it.getResolvedType().toJavaCode()} ${it.localName} = ${it.getDefaultValue()};")
+ }
+
+ do {
+ val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList()
+ val mJustRead = arrayListOf<Expr>()
+ while (!batch.none()) {
+ val readNow = batch.filter { it.shouldReadNow(mJustRead) }
+ if (readNow.isEmpty()) {
+ throw IllegalStateException("do not know what I can read. bailing out ${batch.joinToString("\n")}")
+ }
+ Log.d { "new read now. batch size: ${batch.size()}, readNow size: ${readNow.size()}" }
+
+ readNow.forEach {
+ nl(readWithDependants(it, mJustRead, batch, tmpDirtyFlags))
+ }
+ batch.removeAll(mJustRead)
+ }
+ tab("// batch finished")
+ } while(model.markBitsRead())
+
+ //
+ layoutBinder.getBindingTargets().filter { it.isUsed() }
+ .flatMap { it.getBindings() }
+ .groupBy { it.getExpr() }
+ .forEach {
+ val flagSet = it.key.dirtyFlagSet
+ tab("if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
+ "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
+ }.joinToString(" || ")
+ }) {") {
+ it.value.forEach { binding ->
+ tab("// api target ${binding.getMinApi()}")
+ val fieldName : String
+ if (binding.getTarget().getViewClass().
+ equals(binding.getTarget().getInterfaceType())) {
+ fieldName = "this.${binding.getTarget().fieldName}"
+ } else {
+ fieldName = "((${binding.getTarget().getViewClass()}) this.${binding.getTarget().fieldName})"
+ }
+ val bindingCode = binding.toJavaCode(fieldName, binding.getExpr().toCode().generate())
+ if (binding.getMinApi() > 1) {
+ tab("if(getBuildSdkInt() >= ${binding.getMinApi()}) {") {
+ tab("$bindingCode;")
+ }
+ tab("}")
+ } else {
+ tab("$bindingCode;")
+ }
+ }
+ }
+ tab("}")
+ }
+ includedBinders.filter{it.isUsed()}.forEach { binder ->
+ tab("${binder.fieldName}.executePendingBindings();")
+ }
+ layoutBinder.getBindingTargets().filter{
+ it.isUsed() && it.getResolvedType() != null && it.getResolvedType().extendsViewStub()
+ }.forEach {
+ tab("if (${it.fieldName}.getBinding() != null) {") {
+ tab("${it.fieldName}.getBinding().executePendingBindings();")
+ }
+ tab("}")
+ }
+ }
+ nl("}")
+ }
+
+ fun readWithDependants(expr : Expr, mJustRead : MutableList<Expr>, batch : MutableList<Expr>,
+ tmpDirtyFlags : FlagSet, inheritedFlags : FlagSet? = null) : KCode = kcode("") {
+ mJustRead.add(expr)
+ Log.d { expr.getUniqueKey() }
+ val flagSet = expr.shouldReadFlagSet
+ val needsIfWrapper = inheritedFlags == null || !flagSet.bitsEqual(inheritedFlags)
+ val ifClause = "if (${tmpDirtyFlags.mapOr(flagSet){ suffix, index ->
+ "(${tmpDirtyFlags.localValue(index)} & ${flagSet.localValue(index)}) != 0"
+ }.joinToString(" || ")
+ })"
+
+ val readCode = kcode("") {
+ if (!expr.isVariable()) {
+ // it is not a variable read it.
+ tab("// read ${expr.getUniqueKey()}")
+ // create an if case for all dependencies that might be null
+ val nullables = expr.getDependencies().filter {
+ it.isMandatory() && it.getOther().getResolvedType().isNullable()
+ }.map { it.getOther() }
+ if (!expr.isEqualityCheck() && nullables.isNotEmpty()) {
+ tab ("if ( ${nullables.map { "${it.localName} != null" }.joinToString(" && ")}) {") {
+ tab("${expr.localName}").app(" = ", expr.toCode(true)).app(";")
+ }
+ tab("}")
+ } else {
+ tab("${expr.localName}").app(" = ", expr.toCode(true)).app(";")
+ }
+ if (expr.isObservable()) {
+ tab("updateRegistration(${expr.getId()}, ${expr.localName});")
+ }
+ }
+
+ // if I am the condition for an expression, set its flag
+ val conditionals = expr.getDependants().filter { !it.isConditional()
+ && it.getDependant() is TernaryExpr && (it.getDependant() as TernaryExpr).getPred() == expr }
+ .map { it.getDependant() }
+ if (conditionals.isNotEmpty()) {
+ tab("// setting conditional flags")
+ tab("if (${expr.localName}) {") {
+ conditionals.forEach {
+ val set = it.getRequirementFlagSet(true)
+ mDirtyFlags.mapOr(set) { suffix , index ->
+ tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
+ }
+ }
+ }
+ tab("} else {") {
+ conditionals.forEach {
+ val set = it.getRequirementFlagSet(false)
+ mDirtyFlags.mapOr(set) { suffix , index ->
+ tab("${tmpDirtyFlags.localValue(index)} |= ${set.localValue(index)};")
+ }
+ }
+ } tab("}")
+ }
+
+ val chosen = expr.getDependants().filter {
+ val dependant = it.getDependant()
+ batch.contains(dependant) &&
+ dependant.shouldReadFlagSet.andNot(flagSet).isEmpty() &&
+ dependant.shouldReadNow(mJustRead)
+ }
+ if (chosen.isNotEmpty()) {
+ val nextInheritedFlags = if (needsIfWrapper) flagSet else inheritedFlags
+ chosen.forEach {
+ nl(readWithDependants(it.getDependant(), mJustRead, batch, tmpDirtyFlags, nextInheritedFlags))
+ }
+ }
+ }
+ if (needsIfWrapper) {
+ tab(ifClause) {
+ app(" {")
+ nl(readCode)
+ }
+ tab("}")
+ } else {
+ nl(readCode)
+ }
+ }
+
+ fun declareFactories() = kcode("") {
+ nl("public static ${baseClassName} inflate(android.view.ViewGroup root) {") {
+ tab("return bind(android.view.LayoutInflater.from(root.getContext()).inflate(${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, true));")
+ }
+ nl("}")
+ nl("public static ${baseClassName} inflate(android.content.Context context) {") {
+ tab("return bind(android.view.LayoutInflater.from(context).inflate(${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false));")
+ }
+ nl("}")
+ nl("public static ${baseClassName} bind(android.view.View view) {") {
+ tab("if (!\"${layoutBinder.getTag()}\".equals(view.getTag())) {") {
+ tab("throw new RuntimeException(\"view tag isn't correct on view\");")
+ }
+ tab("}")
+ tab("return new ${baseClassName}(view);")
+ }
+ nl("}")
+ }
+
+ public fun writeBaseClass() : String =
+ kcode("package ${layoutBinder.getPackage()};") {
+ nl("import android.databinding.Bindable;")
+ nl("import android.databinding.DataBindingUtil;")
+ nl("import android.databinding.ViewDataBinding;")
+ nl("public abstract class ${baseClassName} extends ViewDataBinding {")
+ layoutBinder.getBindingTargets().filter{it.getId() != null}.forEach {
+ tab("public final ${it.interfaceType} ${it.fieldName};")
+ }
+ nl("")
+ tab("protected ${baseClassName}(android.view.View root_, int localFieldCount") {
+ layoutBinder.getBindingTargets().filter{it.getId() != null}.forEach {
+ tab(", ${it.interfaceType} ${it.readableUniqueName}")
+ }
+ }
+ tab(") {") {
+ tab("super(root_, localFieldCount);")
+ layoutBinder.getBindingTargets().filter{it.getId() != null}.forEach {
+ tab("this.${it.fieldName} = ${it.readableUniqueName};")
+ }
+ }
+ tab("}")
+ nl("")
+ variables.forEach {
+ if (it.getUserDefinedType() != null) {
+ //it.getExpandedUserDefinedType(ModelAnalyzer.getInstance());
+ val type = ModelAnalyzer.getInstance().applyImports(it.getUserDefinedType(), model.getImports())
+ tab("public abstract void ${it.setterName}(${type} ${it.readableUniqueName});")
+ }
+ }
+ tab("public static ${baseClassName} inflate(android.view.ViewGroup root) {") {
+ tab("return DataBindingUtil.<${baseClassName}>inflate(root.getContext(), ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, root, true);")
+ }
+ tab("}")
+ tab("public static ${baseClassName} inflate(android.content.Context context) {") {
+ tab("return DataBindingUtil.<${baseClassName}>inflate(context, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()}, null, false);")
+ }
+ tab("}")
+ tab("public static ${baseClassName} bind(android.view.View view) {") {
+ tab("return (${baseClassName})DataBindingUtil.bindTo(view, ${layoutBinder.getModulePackage()}.R.layout.${layoutBinder.getLayoutname()});")
+ }
+ tab("}")
+ nl("}")
+ }.generate()
+}
diff --git a/tools/data-binding/compiler/src/test/java/android/databinding/CallbackRegistryTest.java b/tools/data-binding/compiler/src/test/java/android/databinding/CallbackRegistryTest.java
new file mode 100644
index 0000000..fd1562d
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/android/databinding/CallbackRegistryTest.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class CallbackRegistryTest {
+
+ final Integer callback1 = 1;
+ final Integer callback2 = 2;
+ final Integer callback3 = 3;
+ CallbackRegistry<Integer, CallbackRegistryTest, Integer> registry;
+ int notify1;
+ int notify2;
+ int notify3;
+ int[] deepNotifyCount = new int[300];
+ Integer argValue;
+
+ private void addNotifyCount(Integer callback) {
+ if (callback == callback1) {
+ notify1++;
+ } else if (callback == callback2) {
+ notify2++;
+ } else if (callback == callback3) {
+ notify3++;
+ }
+ deepNotifyCount[callback]++;
+ }
+
+ @Test
+ public void testAddListener() {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg, Integer arg2) {
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ Integer callback = 0;
+
+ assertNotNull(registry.copyListeners());
+ assertEquals(0, registry.copyListeners().size());
+
+ registry.add(callback);
+ ArrayList<Integer> callbacks = registry.copyListeners();
+ assertEquals(1, callbacks.size());
+ assertEquals(callback, callbacks.get(0));
+
+ registry.add(callback);
+ callbacks = registry.copyListeners();
+ assertEquals(1, callbacks.size());
+ assertEquals(callback, callbacks.get(0));
+
+ Integer otherListener = 1;
+ registry.add(otherListener);
+ callbacks = registry.copyListeners();
+ assertEquals(2, callbacks.size());
+ assertEquals(callback, callbacks.get(0));
+ assertEquals(otherListener, callbacks.get(1));
+
+ registry.remove(callback);
+ registry.add(callback);
+ callbacks = registry.copyListeners();
+ assertEquals(2, callbacks.size());
+ assertEquals(callback, callbacks.get(1));
+ assertEquals(otherListener, callbacks.get(0));
+ }
+
+ @Test
+ public void testSimpleNotify() {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg1, Integer arg) {
+ assertEquals(arg1, (int) arg);
+ addNotifyCount(callback);
+ argValue = arg;
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ registry.add(callback2);
+ Integer arg = 1;
+ registry.notifyCallbacks(this, arg, arg);
+ assertEquals(arg, argValue);
+ assertEquals(1, notify2);
+ }
+
+ @Test
+ public void testRemoveWhileNotifying() {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg1, Integer arg) {
+ addNotifyCount(callback);
+ if (callback == callback1) {
+ registry.remove(callback1);
+ registry.remove(callback2);
+ }
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ registry.add(callback1);
+ registry.add(callback2);
+ registry.add(callback3);
+ registry.notifyCallbacks(this, 0, null);
+ assertEquals(1, notify1);
+ assertEquals(1, notify2);
+ assertEquals(1, notify3);
+
+ ArrayList<Integer> callbacks = registry.copyListeners();
+ assertEquals(1, callbacks.size());
+ assertEquals(callback3, callbacks.get(0));
+ }
+
+ @Test
+ public void testDeepRemoveWhileNotifying() {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg1, Integer arg) {
+ addNotifyCount(callback);
+ registry.remove(callback);
+ registry.notifyCallbacks(CallbackRegistryTest.this, arg1, null);
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ registry.add(callback1);
+ registry.add(callback2);
+ registry.add(callback3);
+ registry.notifyCallbacks(this, 0, null);
+ assertEquals(1, notify1);
+ assertEquals(2, notify2);
+ assertEquals(3, notify3);
+
+ ArrayList<Integer> callbacks = registry.copyListeners();
+ assertEquals(0, callbacks.size());
+ }
+
+ @Test
+ public void testAddRemovedListener() {
+
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg1, Integer arg) {
+ addNotifyCount(callback);
+ if (callback == callback1) {
+ registry.remove(callback2);
+ } else if (callback == callback3) {
+ registry.add(callback2);
+ }
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+
+ registry.add(callback1);
+ registry.add(callback2);
+ registry.add(callback3);
+ registry.notifyCallbacks(this, 0, null);
+
+ ArrayList<Integer> callbacks = registry.copyListeners();
+ assertEquals(3, callbacks.size());
+ assertEquals(callback1, callbacks.get(0));
+ assertEquals(callback3, callbacks.get(1));
+ assertEquals(callback2, callbacks.get(2));
+ assertEquals(1, notify1);
+ assertEquals(1, notify2);
+ assertEquals(1, notify3);
+ }
+
+ @Test
+ public void testVeryDeepRemoveWhileNotifying() {
+ final Integer[] callbacks = new Integer[deepNotifyCount.length];
+ for (int i = 0; i < callbacks.length; i++) {
+ callbacks[i] = i;
+ }
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg1, Integer arg) {
+ addNotifyCount(callback);
+ registry.remove(callback);
+ registry.remove(callbacks[callbacks.length - callback - 1]);
+ registry.notifyCallbacks(CallbackRegistryTest.this, arg1, null);
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ for (int i = 0; i < callbacks.length; i++) {
+ registry.add(callbacks[i]);
+ }
+ registry.notifyCallbacks(this, 0, null);
+ for (int i = 0; i < deepNotifyCount.length; i++) {
+ int expectedCount = Math.min(i + 1, deepNotifyCount.length - i);
+ assertEquals(expectedCount, deepNotifyCount[i]);
+ }
+
+ ArrayList<Integer> callbackList = registry.copyListeners();
+ assertEquals(0, callbackList.size());
+ }
+
+ @Test
+ public void testClear() {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg1, Integer arg) {
+ addNotifyCount(callback);
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ for (int i = 0; i < deepNotifyCount.length; i++) {
+ registry.add(i);
+ }
+ registry.clear();
+
+ ArrayList<Integer> callbackList = registry.copyListeners();
+ assertEquals(0, callbackList.size());
+
+ registry.notifyCallbacks(this, 0, null);
+ for (int i = 0; i < deepNotifyCount.length; i++) {
+ assertEquals(0, deepNotifyCount[i]);
+ }
+ }
+
+ @Test
+ public void testNestedClear() {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg1, Integer arg) {
+ addNotifyCount(callback);
+ registry.clear();
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ for (int i = 0; i < deepNotifyCount.length; i++) {
+ registry.add(i);
+ }
+ registry.notifyCallbacks(this, 0, null);
+ for (int i = 0; i < deepNotifyCount.length; i++) {
+ assertEquals(1, deepNotifyCount[i]);
+ }
+
+ ArrayList<Integer> callbackList = registry.copyListeners();
+ assertEquals(0, callbackList.size());
+ }
+
+ @Test
+ public void testIsEmpty() throws Exception {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg, Integer arg2) {
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+ Integer callback = 0;
+
+ assertTrue(registry.isEmpty());
+ registry.add(callback);
+ assertFalse(registry.isEmpty());
+ }
+
+ @Test
+ public void testClone() throws Exception {
+ CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier =
+ new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() {
+ @Override
+ public void onNotifyCallback(Integer callback, CallbackRegistryTest sender,
+ int arg, Integer arg2) {
+ }
+ };
+ registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier);
+
+ assertTrue(registry.isEmpty());
+ CallbackRegistry<Integer, CallbackRegistryTest, Integer> registry2 = registry.clone();
+ Integer callback = 0;
+ registry.add(callback);
+ assertFalse(registry.isEmpty());
+ assertTrue(registry2.isEmpty());
+ registry2 = registry.clone();
+ assertFalse(registry2.isEmpty());
+ }
+}
diff --git a/tools/data-binding/compiler/src/test/java/android/databinding/tool/ExpressionVisitorTest.java b/tools/data-binding/compiler/src/test/java/android/databinding/tool/ExpressionVisitorTest.java
new file mode 100644
index 0000000..e3f3345
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/android/databinding/tool/ExpressionVisitorTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.databinding.tool.expr.ComparisonExpr;
+import android.databinding.tool.expr.Dependency;
+import android.databinding.tool.expr.Expr;
+import android.databinding.tool.expr.ExprModel;
+import android.databinding.tool.expr.FieldAccessExpr;
+import android.databinding.tool.expr.IdentifierExpr;
+import android.databinding.tool.expr.MethodCallExpr;
+import android.databinding.tool.expr.SymbolExpr;
+import android.databinding.tool.expr.TernaryExpr;
+import android.databinding.tool.reflection.Callable;
+import android.databinding.tool.reflection.java.JavaAnalyzer;
+import android.databinding.tool.reflection.java.JavaClass;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class ExpressionVisitorTest {
+ ExpressionParser mParser = new ExpressionParser(new ExprModel());
+
+ @Before
+ public void setUp() throws Exception {
+ JavaAnalyzer.initForTests();
+ }
+
+ private <T extends Expr> T parse(String input, Class<T> klass) {
+ final Expr parsed = mParser.parse(input);
+ assertSame(klass, parsed.getClass());
+ return (T) parsed;
+ }
+
+ @Test
+ public void testSymbol() {
+ final SymbolExpr res = parse("null", SymbolExpr.class);
+ assertEquals(1, mParser.getModel().size());
+ assertEquals("null", res.getText());
+ assertEquals(new JavaClass(Object.class),res.getResolvedType());
+ assertEquals(0, res.getDependencies().size());
+ }
+
+
+ @RunWith(Parameterized.class)
+ public static class ComparisonExprTests {
+ ExpressionParser mParser = new ExpressionParser(new ExprModel());
+ private final String mOp;
+
+ @Before
+ public void setUp() throws Exception {
+ JavaAnalyzer.initForTests();
+ }
+
+ @Parameterized.Parameters
+ public static List<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ {"=="}, {"<="}, {">="}, {">"}, {"<"}
+ });
+ }
+
+ public ComparisonExprTests(String op) {
+ mOp = op;
+ }
+
+ @Test
+ public void testComparison() {
+ final Expr res = mParser.parse("3 " + mOp + " 5");
+ assertEquals(3, mParser.getModel().size());
+ assertTrue(res instanceof ComparisonExpr);
+ // 0 because they are both static
+ assertEquals(0, res.getDependencies().size());
+ }
+ }
+
+
+
+ @Test
+ public void testSimpleFieldAccess() {
+ final FieldAccessExpr expr = parse("a.b", FieldAccessExpr.class);
+ assertEquals(2, mParser.mModel.size());
+ assertEquals("b", expr.getName());
+ assertEquals(1, expr.getChildren().size());
+ final Expr parent = expr.getChildren().get(0);
+ assertTrue(parent instanceof IdentifierExpr);
+ final IdentifierExpr id = (IdentifierExpr) parent;
+ assertEquals("a", id.getName());
+ assertEquals(0, id.getDependencies().size());
+ assertEquals(1, expr.getDependencies().size());
+ }
+
+ @Test
+ public void testIdentifier() {
+ final IdentifierExpr id = parse("myStr", IdentifierExpr.class);
+ assertEquals(1, mParser.mModel.size());
+ assertEquals("myStr", id.getName());
+ id.setUserDefinedType("java.lang.String");
+ assertEquals(new JavaClass(String.class), id.getResolvedType());
+ }
+
+ @Test
+ public void testTernary() {
+ final TernaryExpr parsed = parse("a > b ? 5 : 4", TernaryExpr.class);
+ assertEquals(6, mParser.getModel().size());
+ assertTrue(parsed.getPred() instanceof ComparisonExpr);
+ assertTrue(parsed.getIfTrue() instanceof SymbolExpr);
+ assertTrue(parsed.getIfFalse() instanceof SymbolExpr);
+ ComparisonExpr pred = (ComparisonExpr) parsed.getPred();
+ SymbolExpr ifTrue = (SymbolExpr) parsed.getIfTrue();
+ SymbolExpr ifFalse = (SymbolExpr) parsed.getIfFalse();
+ assertEquals("5", ifTrue.getText());
+ assertEquals("4", ifFalse.getText());
+ assertEquals(1, parsed.getDependencies().size());
+ for (Dependency dependency : parsed.getDependencies()) {
+ assertEquals(dependency.getOther() != pred, dependency.isConditional());
+ }
+ }
+
+ @Test
+ public void testInheritedFieldResolution() {
+ final FieldAccessExpr parsed = parse("myStr.length", FieldAccessExpr.class);
+ assertTrue(parsed.getChild() instanceof IdentifierExpr);
+ final IdentifierExpr id = (IdentifierExpr) parsed.getChild();
+ id.setUserDefinedType("java.lang.String");
+ assertEquals(new JavaClass(int.class), parsed.getResolvedType());
+ Callable getter = parsed.getGetter();
+ assertEquals(Callable.Type.METHOD, getter.type);
+ assertEquals("length", getter.name);
+ assertEquals(1, parsed.getDependencies().size());
+ final Dependency dep = parsed.getDependencies().get(0);
+ assertSame(id, dep.getOther());
+ assertFalse(dep.isConditional());
+ }
+
+ @Test
+ public void testGetterResolution() {
+ final FieldAccessExpr parsed = parse("myStr.bytes", FieldAccessExpr.class);
+ assertTrue(parsed.getChild() instanceof IdentifierExpr);
+ final IdentifierExpr id = (IdentifierExpr) parsed.getChild();
+ id.setUserDefinedType("java.lang.String");
+ assertEquals(new JavaClass(byte[].class), parsed.getResolvedType());
+ Callable getter = parsed.getGetter();
+ assertEquals(Callable.Type.METHOD, getter.type);
+ assertEquals("getBytes", getter.name);
+ assertEquals(1, parsed.getDependencies().size());
+ final Dependency dep = parsed.getDependencies().get(0);
+ assertSame(id, dep.getOther());
+ assertFalse(dep.isConditional());
+ }
+
+ @Test
+ public void testMethodCall() {
+ final MethodCallExpr parsed = parse("user.getName()", MethodCallExpr.class);
+ assertTrue(parsed.getTarget() instanceof IdentifierExpr);
+ assertEquals("getName", parsed.getName());
+ assertEquals(0, parsed.getArgs().size());
+ assertEquals(1, parsed.getDependencies().size());
+ final Dependency dep = parsed.getDependencies().get(0);
+ assertSame(mParser.parse("user"), dep.getOther());
+ assertFalse(dep.isConditional());
+ }
+
+ @Test
+ public void testMethodCallWithArgs() {
+ final MethodCallExpr parsed = parse("str.substring(1, a)", MethodCallExpr.class);
+ assertTrue(parsed.getTarget() instanceof IdentifierExpr);
+ assertEquals("substring", parsed.getName());
+ final List<Expr> args = parsed.getArgs();
+ assertEquals(2, args.size());
+ assertTrue(args.get(0) instanceof SymbolExpr);
+ assertTrue(args.get(1) instanceof IdentifierExpr);
+ final List<Dependency> deps = parsed.getDependencies();
+ assertEquals(2, deps.size());
+ }
+
+}
diff --git a/tools/data-binding/compiler/src/test/java/android/databinding/tool/LayoutBinderTest.java b/tools/data-binding/compiler/src/test/java/android/databinding/tool/LayoutBinderTest.java
new file mode 100644
index 0000000..4219647
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/android/databinding/tool/LayoutBinderTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool;
+
+
+import org.junit.Before;
+import org.junit.Test;
+
+import android.databinding.tool.expr.Expr;
+import android.databinding.tool.expr.ExprModel;
+import android.databinding.tool.expr.FieldAccessExpr;
+import android.databinding.tool.expr.IdentifierExpr;
+import android.databinding.tool.expr.StaticIdentifierExpr;
+import android.databinding.tool.reflection.Callable;
+import android.databinding.tool.reflection.java.JavaClass;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class LayoutBinderTest {
+ LayoutBinder mLayoutBinder;
+ ExprModel mExprModel;
+ @Before
+ public void setUp() throws Exception {
+ mLayoutBinder = new MockLayoutBinder();
+ mExprModel = mLayoutBinder.getModel();
+ }
+
+ @Test
+ public void testRegisterId() {
+ mLayoutBinder.addVariable("test", "java.lang.String");
+ assertEquals(1, mExprModel.size());
+ final Map.Entry<String, Expr> entry = mExprModel.getExprMap().entrySet().iterator().next();
+ final Expr value = entry.getValue();
+ assertEquals(value.getClass(), IdentifierExpr.class);
+ final IdentifierExpr id = (IdentifierExpr) value;
+ assertEquals("test", id.getName());
+ assertEquals(new JavaClass(String.class), id.getResolvedType());
+ assertTrue(id.isDynamic());
+ }
+
+ @Test
+ public void testRegisterImport() {
+ mExprModel.addImport("test", "java.lang.String");
+ assertEquals(1, mExprModel.size());
+ final Map.Entry<String, Expr> entry = mExprModel.getExprMap().entrySet().iterator().next();
+ final Expr value = entry.getValue();
+ assertEquals(value.getClass(), StaticIdentifierExpr.class);
+ final IdentifierExpr id = (IdentifierExpr) value;
+ assertEquals("test", id.getName());
+ assertEquals(new JavaClass(String.class), id.getResolvedType());
+ assertFalse(id.isDynamic());
+ }
+
+ @Test
+ public void testParse() {
+ mLayoutBinder.addVariable("user", "android.databinding.tool2.LayoutBinderTest.TestUser");
+ mLayoutBinder.parse("user.name");
+ mLayoutBinder.parse("user.lastName");
+ assertEquals(3, mExprModel.size());
+ final List<Expr> bindingExprs = mExprModel.getBindingExpressions();
+ assertEquals(2, bindingExprs.size());
+ IdentifierExpr id = mExprModel.identifier("user");
+ assertTrue(bindingExprs.get(0) instanceof FieldAccessExpr);
+ assertTrue(bindingExprs.get(1) instanceof FieldAccessExpr);
+ assertEquals(2, id.getParents().size());
+ assertTrue(bindingExprs.get(0).getChildren().contains(id));
+ assertTrue(bindingExprs.get(1).getChildren().contains(id));
+ }
+
+ @Test
+ public void testParseWithMethods() {
+ mLayoutBinder.addVariable("user", "android.databinding.tool.LayoutBinderTest.TestUser");
+ mLayoutBinder.parse("user.fullName");
+ Expr item = mExprModel.getBindingExpressions().get(0);
+ assertTrue(item instanceof FieldAccessExpr);
+ IdentifierExpr id = mExprModel.identifier("user");
+ FieldAccessExpr fa = (FieldAccessExpr) item;
+ fa.getResolvedType();
+ final Callable getter = fa.getGetter();
+ assertTrue(getter.type == Callable.Type.METHOD);
+ assertSame(id, fa.getChild());
+ assertTrue(fa.isDynamic());
+ }
+
+ static class TestUser {
+ public String name;
+ public String lastName;
+
+ public String fullName() {
+ return name + " " + lastName;
+ }
+ }
+}
diff --git a/tools/data-binding/compiler/src/test/java/android/databinding/tool/MockLayoutBinder.java b/tools/data-binding/compiler/src/test/java/android/databinding/tool/MockLayoutBinder.java
new file mode 100644
index 0000000..068de85
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/android/databinding/tool/MockLayoutBinder.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool;
+
+import android.databinding.tool.store.ResourceBundle;
+
+public class MockLayoutBinder extends LayoutBinder {
+
+ public MockLayoutBinder() {
+ super(new ResourceBundle("com.test"),
+ new ResourceBundle.LayoutFileBundle("blah.xml", 1, ".", "com.test.submodule"));
+ }
+}
\ No newline at end of file
diff --git a/tools/data-binding/compiler/src/test/java/android/databinding/tool/SdkVersionTest.java b/tools/data-binding/compiler/src/test/java/android/databinding/tool/SdkVersionTest.java
new file mode 100644
index 0000000..70db3a7
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/android/databinding/tool/SdkVersionTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.ModelMethod;
+import android.databinding.tool.reflection.SdkUtil;
+import android.databinding.tool.reflection.java.JavaAnalyzer;
+
+import static org.junit.Assert.assertEquals;
+
+public class SdkVersionTest {
+
+ @Before
+ public void setUp() throws Exception {
+ JavaAnalyzer.initForTests();
+ }
+
+ @Test
+ public void testNewApiMethod() {
+ ModelClass view = ModelAnalyzer.getInstance().findClass("android.view.View", null);
+ ModelMethod setElevation = view.getMethods("setElevation", 1)[0];
+ assertEquals(21, SdkUtil.getMinApi(setElevation));
+ }
+
+ @Test
+ public void testCustomCode() {
+ ModelClass view = ModelAnalyzer.getInstance()
+ .findClass("android.databinding.tool.SdkVersionTest", null);
+ ModelMethod setElevation = view.getMethods("testCustomCode", 0)[0];
+ assertEquals(1, SdkUtil.getMinApi(setElevation));
+ }
+}
diff --git a/tools/data-binding/compiler/src/test/java/android/databinding/tool/expr/ExprModelTest.java b/tools/data-binding/compiler/src/test/java/android/databinding/tool/expr/ExprModelTest.java
new file mode 100644
index 0000000..7bccd6e
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/android/databinding/tool/expr/ExprModelTest.java
@@ -0,0 +1,564 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.NotImplementedException;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import android.databinding.tool.LayoutBinder;
+import android.databinding.tool.MockLayoutBinder;
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.java.JavaAnalyzer;
+import android.databinding.tool.util.L;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class ExprModelTest {
+
+ private static class DummyExpr extends Expr {
+
+ String mKey;
+
+ public DummyExpr(String key, DummyExpr... children) {
+ super(children);
+ mKey = key;
+ }
+
+ @Override
+ protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+ return modelAnalyzer.findClass(Integer.class);
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ return constructDynamicChildrenDependencies();
+ }
+
+ @Override
+ protected String computeUniqueKey() {
+ return mKey + super.computeUniqueKey();
+ }
+ }
+
+ ExprModel mExprModel;
+
+ @Rule
+ public TestWatcher mTestWatcher = new TestWatcher() {
+ @Override
+ protected void failed(Throwable e, Description description) {
+ if (mExprModel != null && mExprModel.getFlagMapping() != null) {
+ final String[] mapping = mExprModel.getFlagMapping();
+ for (int i = 0; i < mapping.length; i++) {
+ L.d("flag %d: %s", i, mapping[i]);
+ }
+ }
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ JavaAnalyzer.initForTests();
+ mExprModel = new ExprModel();
+ }
+
+ @Test
+ public void testAddNormal() {
+ final DummyExpr d = new DummyExpr("a");
+ assertSame(d, mExprModel.register(d));
+ assertSame(d, mExprModel.register(d));
+ assertEquals(1, mExprModel.mExprMap.size());
+ }
+
+ @Test
+ public void testAddDupe1() {
+ final DummyExpr d = new DummyExpr("a");
+ assertSame(d, mExprModel.register(d));
+ assertSame(d, mExprModel.register(new DummyExpr("a")));
+ assertEquals(1, mExprModel.mExprMap.size());
+ }
+
+ @Test
+ public void testAddMultiple() {
+ mExprModel.register(new DummyExpr("a"));
+ mExprModel.register(new DummyExpr("b"));
+ assertEquals(2, mExprModel.mExprMap.size());
+ }
+
+
+ @Test
+ public void testAddWithChildren() {
+ DummyExpr a = new DummyExpr("a");
+ DummyExpr b = new DummyExpr("b");
+ DummyExpr c = new DummyExpr("c", a, b);
+ mExprModel.register(c);
+ DummyExpr a2 = new DummyExpr("a");
+ DummyExpr b2 = new DummyExpr("b");
+ DummyExpr c2 = new DummyExpr("c", a, b);
+ assertEquals(c, mExprModel.register(c2));
+ }
+
+ @Test
+ public void testShouldRead() {
+ LayoutBinder lb = new MockLayoutBinder();
+ mExprModel = lb.getModel();
+ IdentifierExpr a = lb.addVariable("a", "java.lang.String");
+ IdentifierExpr b = lb.addVariable("b", "java.lang.String");
+ IdentifierExpr c = lb.addVariable("c", "java.lang.String");
+ lb.parse("a == null ? b : c");
+ mExprModel.comparison("==", a, mExprModel.symbol("null", Object.class));
+ lb.getModel().seal();
+ Iterable<Expr> shouldRead = getShouldRead();
+ // a and a == null
+ assertEquals(2, Iterables.size(shouldRead));
+ final Iterable<Expr> readFirst = getReadFirst(shouldRead, null);
+ assertEquals(1, Iterables.size(readFirst));
+ final Expr first = Iterables.getFirst(readFirst, null);
+ assertSame(a, first);
+ // now , assume we've read this
+ final BitSet shouldReadFlags = first.getShouldReadFlags();
+ assertNotNull(shouldReadFlags);
+ }
+
+ @Test
+ public void testTernaryWithPlus() {
+ LayoutBinder lb = new MockLayoutBinder();
+ mExprModel = lb.getModel();
+ IdentifierExpr user = lb
+ .addVariable("user", "android.databinding.tool.expr.ExprModelTest.User");
+ MathExpr parsed = parse(lb, "user.name + \" \" + (user.lastName ?? \"\")", MathExpr.class);
+ mExprModel.seal();
+ Iterable<Expr> toRead = getShouldRead();
+ Iterable<Expr> readNow = getReadFirst(toRead);
+ assertEquals(1, Iterables.size(readNow));
+ assertSame(user, Iterables.getFirst(readNow, null));
+ List<Expr> justRead = new ArrayList<Expr>();
+ justRead.add(user);
+ readNow = filterOut(getReadFirst(toRead, justRead), justRead);
+ assertEquals(2, Iterables.size(readNow)); //user.name && user.lastName
+ Iterables.addAll(justRead, readNow);
+ // user.lastname (T, F), user.name + " "
+ readNow = filterOut(getReadFirst(toRead, justRead), justRead);
+ assertEquals(2, Iterables.size(readNow)); //user.name && user.lastName
+ Iterables.addAll(justRead, readNow);
+ readNow = filterOut(getReadFirst(toRead, justRead), justRead);
+ assertEquals(0, Iterables.size(readNow));
+ mExprModel.markBitsRead();
+
+ toRead = getShouldRead();
+ assertEquals(2, Iterables.size(toRead));
+ justRead.clear();
+ readNow = filterOut(getReadFirst(toRead, justRead), justRead);
+ assertEquals(1, Iterables.size(readNow));
+ assertSame(parsed.getRight(), Iterables.getFirst(readNow, null));
+ Iterables.addAll(justRead, readNow);
+
+ readNow = filterOut(getReadFirst(toRead, justRead), justRead);
+ assertEquals(1, Iterables.size(readNow));
+ assertSame(parsed, Iterables.getFirst(readNow, null));
+ Iterables.addAll(justRead, readNow);
+
+ readNow = filterOut(getReadFirst(toRead, justRead), justRead);
+ assertEquals(0, Iterables.size(readNow));
+ mExprModel.markBitsRead();
+ assertEquals(0, Iterables.size(getShouldRead()));
+ }
+
+ private List<Expr> filterOut(Iterable itr, final Iterable exclude) {
+ return Arrays.asList(Iterables.toArray(Iterables.filter(itr, new Predicate() {
+ @Override
+ public boolean apply(Object input) {
+ return !Iterables.contains(exclude, input);
+ }
+ }), Expr.class));
+ }
+
+ @Test
+ public void testTernaryInsideTernary() {
+ LayoutBinder lb = new MockLayoutBinder();
+ mExprModel = lb.getModel();
+ IdentifierExpr cond1 = lb.addVariable("cond1", "boolean");
+ IdentifierExpr cond2 = lb.addVariable("cond2", "boolean");
+
+ IdentifierExpr a = lb.addVariable("a", "boolean");
+ IdentifierExpr b = lb.addVariable("b", "boolean");
+ IdentifierExpr c = lb.addVariable("c", "boolean");
+
+ final TernaryExpr ternaryExpr = parse(lb, "cond1 ? cond2 ? a : b : c", TernaryExpr.class);
+ final TernaryExpr innerTernary = (TernaryExpr) ternaryExpr.getIfTrue();
+ mExprModel.seal();
+
+ Iterable<Expr> toRead = getShouldRead();
+ assertEquals(1, Iterables.size(toRead));
+ assertEquals(ternaryExpr.getPred(), Iterables.getFirst(toRead, null));
+
+ Iterable<Expr> readNow = getReadFirst(toRead);
+ assertEquals(1, Iterables.size(readNow));
+ assertEquals(ternaryExpr.getPred(), Iterables.getFirst(readNow, null));
+ int cond1True = ternaryExpr.getRequirementFlagIndex(true);
+ int cond1False = ternaryExpr.getRequirementFlagIndex(false);
+ // ok, it is read now.
+ mExprModel.markBitsRead();
+
+ // now it should read cond2 or c, depending on the flag from first
+ toRead = getShouldRead();
+ assertEquals(2, Iterables.size(toRead));
+ assertExactMatch(toRead, ternaryExpr.getIfFalse(), innerTernary.getPred());
+ assertFlags(ternaryExpr.getIfFalse(), cond1False);
+ assertFlags(ternaryExpr.getIfTrue(), cond1True);
+
+ mExprModel.markBitsRead();
+
+ // now it should read a or b, innerTernary, outerTernary
+ toRead = getShouldRead();
+ assertExactMatch(toRead, innerTernary.getIfTrue(), innerTernary.getIfFalse(), ternaryExpr,
+ innerTernary);
+ assertFlags(innerTernary.getIfTrue(), innerTernary.getRequirementFlagIndex(true));
+ assertFlags(innerTernary.getIfFalse(), innerTernary.getRequirementFlagIndex(false));
+ assertFalse(mExprModel.markBitsRead());
+ }
+
+ @Test
+ public void testRequirementFlags() {
+ LayoutBinder lb = new MockLayoutBinder();
+ mExprModel = lb.getModel();
+ IdentifierExpr a = lb.addVariable("a", "java.lang.String");
+ IdentifierExpr b = lb.addVariable("b", "java.lang.String");
+ IdentifierExpr c = lb.addVariable("c", "java.lang.String");
+ IdentifierExpr d = lb.addVariable("d", "java.lang.String");
+ IdentifierExpr e = lb.addVariable("e", "java.lang.String");
+ final Expr aTernary = lb.parse("a == null ? b == null ? c : d : e");
+ assertTrue(aTernary instanceof TernaryExpr);
+ final Expr bTernary = ((TernaryExpr) aTernary).getIfTrue();
+ assertTrue(bTernary instanceof TernaryExpr);
+ final Expr aIsNull = mExprModel
+ .comparison("==", a, mExprModel.symbol("null", Object.class));
+ final Expr bIsNull = mExprModel
+ .comparison("==", b, mExprModel.symbol("null", Object.class));
+ lb.getModel().seal();
+ Iterable<Expr> shouldRead = getShouldRead();
+ // a and a == null
+ assertEquals(2, Iterables.size(shouldRead));
+ assertFalse(a.getShouldReadFlags().isEmpty());
+ assertTrue(a.getShouldReadFlags().get(a.getId()));
+ assertTrue(b.getShouldReadFlags().isEmpty());
+ assertTrue(c.getShouldReadFlags().isEmpty());
+ assertTrue(d.getShouldReadFlags().isEmpty());
+ assertTrue(e.getShouldReadFlags().isEmpty());
+
+ Iterable<Expr> readFirst = getReadFirst(shouldRead, null);
+ assertEquals(1, Iterables.size(readFirst));
+ final Expr first = Iterables.getFirst(readFirst, null);
+ assertSame(a, first);
+ assertTrue(mExprModel.markBitsRead());
+ for (Expr expr : mExprModel.getPendingExpressions()) {
+ assertNull(expr.mShouldReadFlags);
+ }
+ shouldRead = getShouldRead();
+ assertExactMatch(shouldRead, e, b, bIsNull);
+
+ assertFlags(e, aTernary.getRequirementFlagIndex(false));
+
+ assertFlags(b, aTernary.getRequirementFlagIndex(true));
+ assertFlags(bIsNull, aTernary.getRequirementFlagIndex(true));
+ assertTrue(mExprModel.markBitsRead());
+ shouldRead = getShouldRead();
+ assertEquals(4, Iterables.size(shouldRead));
+ assertTrue(Iterables.contains(shouldRead, c));
+ assertTrue(Iterables.contains(shouldRead, d));
+ assertTrue(Iterables.contains(shouldRead, aTernary));
+ assertTrue(Iterables.contains(shouldRead, bTernary));
+
+ assertTrue(c.getShouldReadFlags().get(bTernary.getRequirementFlagIndex(true)));
+ assertEquals(1, c.getShouldReadFlags().cardinality());
+
+ assertTrue(d.getShouldReadFlags().get(bTernary.getRequirementFlagIndex(false)));
+ assertEquals(1, d.getShouldReadFlags().cardinality());
+
+ assertTrue(bTernary.getShouldReadFlags().get(aTernary.getRequirementFlagIndex(true)));
+ assertEquals(1, bTernary.getShouldReadFlags().cardinality());
+
+ assertEquals(5, aTernary.getShouldReadFlags().cardinality());
+ for (Expr expr : new Expr[]{a, b, c, d, e}) {
+ assertTrue(aTernary.getShouldReadFlags().get(expr.getId()));
+ }
+
+ readFirst = getReadFirst(shouldRead);
+ assertEquals(2, Iterables.size(readFirst));
+ assertTrue(Iterables.contains(readFirst, c));
+ assertTrue(Iterables.contains(readFirst, d));
+ assertFalse(mExprModel.markBitsRead());
+ }
+
+ @Test
+ public void testPostConditionalDependencies() {
+ LayoutBinder lb = new MockLayoutBinder();
+ mExprModel = lb.getModel();
+
+ IdentifierExpr u1 = lb.addVariable("u1", User.class.getCanonicalName());
+ IdentifierExpr u2 = lb.addVariable("u2", User.class.getCanonicalName());
+ IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName());
+ IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName());
+ IdentifierExpr c = lb.addVariable("c", int.class.getCanonicalName());
+ IdentifierExpr d = lb.addVariable("d", int.class.getCanonicalName());
+ IdentifierExpr e = lb.addVariable("e", int.class.getCanonicalName());
+ TernaryExpr abTernary = parse(lb, "a > b ? u1.name : u2.name", TernaryExpr.class);
+ TernaryExpr bcTernary = parse(lb, "b > c ? u1.getCond(d) ? u1.lastName : u2.lastName : `xx`"
+ + " + u2.getCond(e) ", TernaryExpr.class);
+ Expr abCmp = abTernary.getPred();
+ Expr bcCmp = bcTernary.getPred();
+ Expr u1GetCondD = ((TernaryExpr) bcTernary.getIfTrue()).getPred();
+ final MathExpr xxPlusU2getCondE = (MathExpr) bcTernary.getIfFalse();
+ Expr u2GetCondE = xxPlusU2getCondE.getRight();
+ Expr u1Name = abTernary.getIfTrue();
+ Expr u2Name = abTernary.getIfFalse();
+ Expr u1LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfTrue();
+ Expr u2LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfFalse();
+
+ mExprModel.seal();
+ Iterable<Expr> shouldRead = getShouldRead();
+
+ assertExactMatch(shouldRead, a, b, c, abCmp, bcCmp);
+
+ Iterable<Expr> firstRead = getReadFirst(shouldRead);
+
+ assertExactMatch(firstRead, a, b, c);
+
+ assertFlags(a, a, b, u1, u2, u1Name, u2Name);
+ assertFlags(b, a, b, u1, u2, u1Name, u2Name, c, d, u1LastName, u2LastName, e);
+ assertFlags(c, b, c, u1, d, u1LastName, u2LastName, e);
+ assertFlags(abCmp, a, b, u1, u2, u1Name, u2Name);
+ assertFlags(bcCmp, b, c, u1, d, u1LastName, u2LastName, e);
+
+ assertTrue(mExprModel.markBitsRead());
+
+ shouldRead = getShouldRead();
+ Expr[] batch = {d, e, u1, u2, u1GetCondD, u2GetCondE, xxPlusU2getCondE, abTernary,
+ abTernary.getIfTrue(), abTernary.getIfFalse()};
+ assertExactMatch(shouldRead, batch);
+ firstRead = getReadFirst(shouldRead);
+ assertExactMatch(firstRead, d, e, u1, u2);
+
+ assertFlags(d, bcTernary.getRequirementFlagIndex(true));
+ assertFlags(e, bcTernary.getRequirementFlagIndex(false));
+ assertFlags(u1, bcTernary.getRequirementFlagIndex(true),
+ abTernary.getRequirementFlagIndex(true));
+ assertFlags(u2, bcTernary.getRequirementFlagIndex(false),
+ abTernary.getRequirementFlagIndex(false));
+
+ assertFlags(u1GetCondD, bcTernary.getRequirementFlagIndex(true));
+ assertFlags(u2GetCondE, bcTernary.getRequirementFlagIndex(false));
+ assertFlags(xxPlusU2getCondE, bcTernary.getRequirementFlagIndex(false));
+ assertFlags(abTernary, a, b, u1, u2, u1Name, u2Name);
+ assertFlags(abTernary.getIfTrue(), abTernary.getRequirementFlagIndex(true));
+ assertFlags(abTernary.getIfFalse(), abTernary.getRequirementFlagIndex(false));
+
+ assertTrue(mExprModel.markBitsRead());
+
+ shouldRead = getShouldRead();
+ // actually, there is no real case to read u1 anymore because if b>c was not true,
+ // u1.getCond(d) will never be set. Right now, we don't have mechanism to figure this out
+ // and also it does not affect correctness (just an unnecessary if stmt)
+ assertExactMatch(shouldRead, u2, u1LastName, u2LastName, bcTernary.getIfTrue(), bcTernary);
+ firstRead = getReadFirst(shouldRead);
+ assertExactMatch(firstRead, u1LastName, u2);
+
+ assertFlags(u1LastName, bcTernary.getIfTrue().getRequirementFlagIndex(true));
+ assertFlags(u2LastName, bcTernary.getIfTrue().getRequirementFlagIndex(false));
+ assertFlags(u2, bcTernary.getIfTrue().getRequirementFlagIndex(false));
+
+ assertFlags(bcTernary.getIfTrue(), bcTernary.getRequirementFlagIndex(true));
+ assertFlags(bcTernary, b, c, u1, u2, d, u1LastName, u2LastName, e);
+ }
+
+ @Test
+ public void testCircularDependency() {
+ LayoutBinder lb = new MockLayoutBinder();
+ mExprModel = lb.getModel();
+ IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName());
+ IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName());
+ final TernaryExpr abTernary = parse(lb, "a > 3 ? a : b", TernaryExpr.class);
+ mExprModel.seal();
+ Iterable<Expr> shouldRead = getShouldRead();
+ assertExactMatch(shouldRead, a, abTernary.getPred());
+ assertTrue(mExprModel.markBitsRead());
+ shouldRead = getShouldRead();
+ assertExactMatch(shouldRead, b, abTernary);
+ assertFalse(mExprModel.markBitsRead());
+ }
+
+ @Test
+ public void testNestedCircularDependency() {
+ LayoutBinder lb = new MockLayoutBinder();
+ mExprModel = lb.getModel();
+ IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName());
+ IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName());
+ IdentifierExpr c = lb.addVariable("c", int.class.getCanonicalName());
+ final TernaryExpr a3Ternary = parse(lb, "a > 3 ? c > 4 ? a : b : c", TernaryExpr.class);
+ final TernaryExpr c4Ternary = (TernaryExpr) a3Ternary.getIfTrue();
+ mExprModel.seal();
+ Iterable<Expr> shouldRead = getShouldRead();
+ assertExactMatch(shouldRead, a, a3Ternary.getPred());
+ assertTrue(mExprModel.markBitsRead());
+ shouldRead = getShouldRead();
+ assertExactMatch(shouldRead, c, c4Ternary.getPred());
+ assertFlags(c, a3Ternary.getRequirementFlagIndex(true),
+ a3Ternary.getRequirementFlagIndex(false));
+ assertFlags(c4Ternary.getPred(), a3Ternary.getRequirementFlagIndex(true));
+ }
+
+ @Test
+ public void testNoFlagsForNonBindingStatic() {
+ LayoutBinder lb = new MockLayoutBinder();
+ mExprModel = lb.getModel();
+ lb.addVariable("a", int.class.getCanonicalName());
+ final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class);
+ mExprModel.seal();
+ assertTrue(parsed.getRight().getInvalidFlags().isEmpty());
+ assertEquals(1, parsed.getLeft().getInvalidFlags().cardinality());
+ assertEquals(1, mExprModel.getInvalidateableFieldLimit());
+ }
+
+ @Test
+ public void testFlagsForBindingStatic() {
+ LayoutBinder lb = new MockLayoutBinder();
+ mExprModel = lb.getModel();
+ lb.addVariable("a", int.class.getCanonicalName());
+ final Expr staticParsed = parse(lb, "3 + 2", MathExpr.class);
+ final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class);
+ mExprModel.seal();
+ assertTrue(staticParsed.isBindingExpression());
+ assertEquals(1, staticParsed.getInvalidFlags().cardinality());
+ assertEquals(parsed.getRight().getInvalidFlags(), staticParsed.getInvalidFlags());
+ assertEquals(1, parsed.getLeft().getInvalidFlags().cardinality());
+ assertEquals(2, mExprModel.getInvalidateableFieldLimit());
+ }
+
+ @Test
+ public void testPartialNeededRead() {
+ throw new NotImplementedException("create a test that has a variable which can be read for "
+ + "some flags and also may be read for some condition. Try both must match and"
+ + " partial match and none-match in conditionals");
+ }
+
+ private void assertFlags(Expr a, int... flags) {
+ BitSet bitset = new BitSet();
+ for (int flag : flags) {
+ bitset.set(flag);
+ }
+ assertEquals("flag test for " + a.getUniqueKey(), bitset, a.getShouldReadFlags());
+ }
+
+ private void assertFlags(Expr a, Expr... exprs) {
+ BitSet bitSet = a.getShouldReadFlags();
+ for (Expr expr : exprs) {
+ BitSet clone = (BitSet) bitSet.clone();
+ clone.and(expr.getInvalidFlags());
+ assertEquals("should read flags of " + a.getUniqueKey() + " should include " + expr
+ .getUniqueKey(), expr.getInvalidFlags(), clone);
+ }
+
+ BitSet composite = new BitSet();
+ for (Expr expr : exprs) {
+ composite.or(expr.getInvalidFlags());
+ }
+ assertEquals("composite flags should match", composite, bitSet);
+ }
+
+ private void assertExactMatch(Iterable<Expr> iterable, Expr... exprs) {
+ int i = 0;
+ log("list", iterable);
+ for (Expr expr : exprs) {
+ assertTrue((i++) + ":must contain " + expr.getUniqueKey(),
+ Iterables.contains(iterable, expr));
+ }
+ i = 0;
+ for (Expr expr : iterable) {
+ assertTrue((i++) + ":must be expected " + expr.getUniqueKey(),
+ ArrayUtils.contains(exprs, expr));
+ }
+ }
+
+ private <T extends Expr> T parse(LayoutBinder binder, String input, Class<T> klass) {
+ final Expr parsed = binder.parse(input);
+ assertTrue(klass.isAssignableFrom(parsed.getClass()));
+ return (T) parsed;
+ }
+
+ private void log(String s, Iterable<Expr> iterable) {
+ L.d(s);
+ for (Expr e : iterable) {
+ L.d(": %s : %s allFlags: %s readSoFar: %s", e.getUniqueKey(), e.getShouldReadFlags(),
+ e.getShouldReadFlagsWithConditionals(), e.getReadSoFar());
+ }
+ L.d("end of %s", s);
+ }
+
+ private Iterable<Expr> getReadFirst(Iterable<Expr> shouldRead) {
+ return getReadFirst(shouldRead, null);
+ }
+
+ private Iterable<Expr> getReadFirst(Iterable<Expr> shouldRead, final Iterable<Expr> justRead) {
+ return Iterables.filter(shouldRead, new Predicate<Expr>() {
+ @Override
+ public boolean apply(Expr input) {
+ return input.shouldReadNow(justRead);
+ }
+ });
+ }
+
+ private Iterable<Expr> getShouldRead() {
+ return mExprModel.filterShouldRead(mExprModel.getPendingExpressions());
+ }
+
+ public static class User {
+
+ String name;
+
+ String lastName;
+
+ public String getName() {
+ return name;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public boolean getCond(int i) {
+ return true;
+ }
+ }
+}
diff --git a/tools/data-binding/compiler/src/test/java/android/databinding/tool/expr/ExprTest.java b/tools/data-binding/compiler/src/test/java/android/databinding/tool/expr/ExprTest.java
new file mode 100644
index 0000000..f051fde
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/android/databinding/tool/expr/ExprTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.expr;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.java.JavaAnalyzer;
+
+import java.util.BitSet;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class ExprTest{
+ private static class DummyExpr extends Expr {
+ String mKey;
+ public DummyExpr(String key, DummyExpr... children) {
+ super(children);
+ mKey = key;
+ }
+
+ @Override
+ protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+ return modelAnalyzer.findClass(Integer.class);
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ return constructDynamicChildrenDependencies();
+ }
+
+ @Override
+ protected String computeUniqueKey() {
+ return mKey + super.computeUniqueKey();
+ }
+
+ @Override
+ public boolean isDynamic() {
+ return true;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ JavaAnalyzer.initForTests();
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testBadExpr() {
+ Expr expr = new Expr() {
+ @Override
+ protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+ return modelAnalyzer.findClass(Integer.class);
+ }
+
+ @Override
+ protected List<Dependency> constructDependencies() {
+ return constructDynamicChildrenDependencies();
+ }
+ };
+ expr.getUniqueKey();
+ }
+
+ @Test
+ public void testBasicInvalidationFlag() {
+ DummyExpr d = new DummyExpr("a");
+ d.setId(3);
+ d.enableDirectInvalidation();
+ assertTrue(d.getInvalidFlags().get(3));
+ }
+
+ @Test
+ public void testCannotBeInvalidated() {
+ DummyExpr d = new DummyExpr("a");
+ d.setId(3);
+ assertTrue(d.getInvalidFlags().isEmpty());
+ }
+
+ @Test
+ public void testInvalidationInheritance() {
+ ExprModel model = new ExprModel();
+ DummyExpr a = model.register(new DummyExpr("a"));
+ DummyExpr b = model.register(new DummyExpr("b"));
+ DummyExpr c = model.register(new DummyExpr("c", a, b));
+ a.enableDirectInvalidation();
+ b.enableDirectInvalidation();
+ c.setBindingExpression(true);
+ model.seal();
+ assertFlags(c, a, b);
+ }
+
+ @Test
+ public void testInvalidationInheritance2() {
+ ExprModel model = new ExprModel();
+ DummyExpr a = model.register(new DummyExpr("a"));
+ DummyExpr b = model.register(new DummyExpr("b", a));
+ DummyExpr c = model.register(new DummyExpr("c", b));
+ a.enableDirectInvalidation();
+ b.enableDirectInvalidation();
+ c.setBindingExpression(true);
+ model.seal();
+ assertFlags(c, a, b);
+ }
+
+ @Test
+ public void testShouldReadFlags() {
+ ExprModel model = new ExprModel();
+ DummyExpr a = model.register(new DummyExpr("a"));
+ a.enableDirectInvalidation();
+ a.setBindingExpression(true);
+ model.seal();
+ assertFlags(a, a);
+ }
+
+ @Test
+ public void testShouldReadDependencyFlags() {
+ ExprModel model = new ExprModel();
+ DummyExpr a = model.register(new DummyExpr("a"));
+ DummyExpr b = model.register(new DummyExpr("b", a));
+ DummyExpr c = model.register(new DummyExpr("c", b));
+ a.enableDirectInvalidation();
+ b.enableDirectInvalidation();
+ b.setBindingExpression(true);
+ c.setBindingExpression(true);
+ model.seal();
+ assertFlags(b, a, b);
+ assertFlags(c, a, b);
+ }
+
+ private void assertFlags(Expr a, Expr... exprs) {
+ BitSet bitSet = a.getShouldReadFlags();
+ for (Expr expr : exprs) {
+ BitSet clone = (BitSet) bitSet.clone();
+ clone.and(expr.getInvalidFlags());
+ assertEquals("should read flags of " + a.getUniqueKey() + " should include " + expr
+ .getUniqueKey(), expr.getInvalidFlags(), clone);
+ }
+
+ BitSet composite = new BitSet();
+ for (Expr expr : exprs) {
+ composite.or(expr.getInvalidFlags());
+ }
+ assertEquals("composite flags should match", composite, bitSet);
+ }
+}
diff --git a/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaAnalyzer.java b/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaAnalyzer.java
new file mode 100644
index 0000000..19b14b4
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaAnalyzer.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.reflection.java;
+
+import com.google.common.collect.ImmutableMap;
+
+import android.databinding.tool.reflection.ModelAnalyzer;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.SdkUtil;
+import android.databinding.tool.reflection.TypeUtil;
+import android.databinding.tool.util.L;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.HashMap;
+import java.util.Map;
+
+public class JavaAnalyzer extends ModelAnalyzer {
+ public static final Map<String, Class> PRIMITIVE_TYPES =
+ new ImmutableMap.Builder<String, Class>()
+ .put("boolean", boolean.class)
+ .put("byte", byte.class)
+ .put("short", short.class)
+ .put("char", char.class)
+ .put("int", int.class)
+ .put("long", long.class)
+ .put("float", float.class)
+ .put("double", double.class)
+ .build();
+
+ private HashMap<String, JavaClass> mClassCache = new HashMap<String, JavaClass>();
+
+ private final ClassLoader mClassLoader;
+
+ public JavaAnalyzer(ClassLoader classLoader) {
+ setInstance(this);
+ mClassLoader = classLoader;
+ }
+
+ @Override
+ public JavaClass loadPrimitive(String className) {
+ Class clazz = PRIMITIVE_TYPES.get(className);
+ if (clazz == null) {
+ return null;
+ } else {
+ return new JavaClass(clazz);
+ }
+ }
+
+ @Override
+ public ModelClass findClass(String className, Map<String, String> imports) {
+ // TODO handle imports
+ JavaClass loaded = mClassCache.get(className);
+ if (loaded != null) {
+ return loaded;
+ }
+ L.d("trying to load class %s from %s", className, mClassLoader.toString());
+ loaded = loadPrimitive(className);
+ if (loaded == null) {
+ try {
+ if (className.startsWith("[") && className.contains("L")) {
+ int indexOfL = className.indexOf('L');
+ JavaClass baseClass = (JavaClass) findClass(
+ className.substring(indexOfL + 1, className.length() - 1), null);
+ String realClassName = className.substring(0, indexOfL + 1) +
+ baseClass.mClass.getCanonicalName() + ';';
+ loaded = new JavaClass(Class.forName(realClassName, false, mClassLoader));
+ mClassCache.put(className, loaded);
+ } else {
+ loaded = loadRecursively(className);
+ mClassCache.put(className, loaded);
+ }
+
+ } catch (Throwable t) {
+// L.e(t, "cannot load class " + className);
+ }
+ }
+ // expr visitor may call this to resolve statics. Sometimes, it is OK not to find a class.
+ if (loaded == null) {
+ return null;
+ }
+ L.d("loaded class %s", loaded.mClass.getCanonicalName());
+ return loaded;
+ }
+
+ @Override
+ public ModelClass findClass(Class classType) {
+ return new JavaClass(classType);
+ }
+
+ @Override
+ public TypeUtil createTypeUtil() {
+ return new JavaTypeUtil();
+ }
+
+ private JavaClass loadRecursively(String className) throws ClassNotFoundException {
+ try {
+ L.d("recursively checking %s", className);
+ return new JavaClass(mClassLoader.loadClass(className));
+ } catch (ClassNotFoundException ex) {
+ int lastIndexOfDot = className.lastIndexOf(".");
+ if (lastIndexOfDot == -1) {
+ throw ex;
+ }
+ return loadRecursively(className.substring(0, lastIndexOfDot) + "$" + className
+ .substring(lastIndexOfDot + 1));
+ }
+ }
+
+ public static void initForTests() {
+ Map<String, String> env = System.getenv();
+ for (Map.Entry<String, String> entry : env.entrySet()) {
+ L.d("%s %s", entry.getKey(), entry.getValue());
+ }
+ String androidHome = env.get("ANDROID_HOME");
+ if (androidHome == null) {
+ throw new IllegalStateException(
+ "you need to have ANDROID_HOME set in your environment"
+ + " to run compiler tests");
+ }
+ File androidJar = new File(androidHome + "/platforms/android-21/android.jar");
+ if (!androidJar.exists() || !androidJar.canRead()) {
+ throw new IllegalStateException(
+ "cannot find android jar at " + androidJar.getAbsolutePath());
+ }
+ // now load android data binding library as well
+
+ try {
+ ClassLoader classLoader = new URLClassLoader(new URL[]{androidJar.toURI().toURL()},
+ ModelAnalyzer.class.getClassLoader());
+ new JavaAnalyzer(classLoader);
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("cannot create class loader", e);
+ }
+
+ SdkUtil.initialize(8, new File(androidHome));
+ }
+}
diff --git a/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaClass.java b/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaClass.java
new file mode 100644
index 0000000..121a569
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaClass.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.reflection.java;
+
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.ModelField;
+import android.databinding.tool.reflection.ModelMethod;
+import android.databinding.tool.reflection.TypeUtil;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+public class JavaClass extends ModelClass {
+ public final Class mClass;
+
+ public JavaClass(Class clazz) {
+ mClass = clazz;
+ }
+
+ @Override
+ public String toJavaCode() {
+ return toJavaCode(mClass);
+ }
+
+ private static String toJavaCode(Class aClass) {
+ if (aClass.isArray()) {
+ Class component = aClass.getComponentType();
+ return toJavaCode(component) + "[]";
+ } else {
+ return aClass.getCanonicalName().replace('$', '.');
+ }
+ }
+
+ @Override
+ public boolean isArray() {
+ return mClass.isArray();
+ }
+
+ @Override
+ public ModelClass getComponentType() {
+ if (mClass.isArray()) {
+ return new JavaClass(mClass.getComponentType());
+ } else if (isList() || isMap()) {
+ return new JavaClass(Object.class);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean isNullable() {
+ return Object.class.isAssignableFrom(mClass);
+ }
+
+ @Override
+ public boolean isPrimitive() {
+ return mClass.isPrimitive();
+ }
+
+ @Override
+ public boolean isBoolean() {
+ return boolean.class.equals(mClass);
+ }
+
+ @Override
+ public boolean isChar() {
+ return char.class.equals(mClass);
+ }
+
+ @Override
+ public boolean isByte() {
+ return byte.class.equals(mClass);
+ }
+
+ @Override
+ public boolean isShort() {
+ return short.class.equals(mClass);
+ }
+
+ @Override
+ public boolean isInt() {
+ return int.class.equals(mClass);
+ }
+
+ @Override
+ public boolean isLong() {
+ return long.class.equals(mClass);
+ }
+
+ @Override
+ public boolean isFloat() {
+ return float.class.equals(mClass);
+ }
+
+ @Override
+ public boolean isDouble() {
+ return double.class.equals(mClass);
+ }
+
+ @Override
+ public boolean isVoid() {
+ return void.class.equals(mClass);
+ }
+
+ @Override
+ public ModelClass unbox() {
+ if (mClass.isPrimitive()) {
+ return this;
+ }
+ if (Integer.class.equals(mClass)) {
+ return new JavaClass(int.class);
+ } else if (Long.class.equals(mClass)) {
+ return new JavaClass(long.class);
+ } else if (Short.class.equals(mClass)) {
+ return new JavaClass(short.class);
+ } else if (Byte.class.equals(mClass)) {
+ return new JavaClass(byte.class);
+ } else if (Character.class.equals(mClass)) {
+ return new JavaClass(char.class);
+ } else if (Double.class.equals(mClass)) {
+ return new JavaClass(double.class);
+ } else if (Float.class.equals(mClass)) {
+ return new JavaClass(float.class);
+ } else if (Boolean.class.equals(mClass)) {
+ return new JavaClass(boolean.class);
+ } else {
+ // not a boxed type
+ return this;
+ }
+
+ }
+
+ @Override
+ public JavaClass box() {
+ if (!mClass.isPrimitive()) {
+ return this;
+ }
+ if (int.class.equals(mClass)) {
+ return new JavaClass(Integer.class);
+ } else if (long.class.equals(mClass)) {
+ return new JavaClass(Long.class);
+ } else if (short.class.equals(mClass)) {
+ return new JavaClass(Short.class);
+ } else if (byte.class.equals(mClass)) {
+ return new JavaClass(Byte.class);
+ } else if (char.class.equals(mClass)) {
+ return new JavaClass(Character.class);
+ } else if (double.class.equals(mClass)) {
+ return new JavaClass(Double.class);
+ } else if (float.class.equals(mClass)) {
+ return new JavaClass(Float.class);
+ } else if (boolean.class.equals(mClass)) {
+ return new JavaClass(Boolean.class);
+ } else {
+ // not a valid type?
+ return this;
+ }
+ }
+
+ @Override
+ public boolean isAssignableFrom(ModelClass that) {
+ Class thatClass = ((JavaClass) that).mClass;
+ return mClass.isAssignableFrom(thatClass);
+ }
+
+ @Override
+ public ModelClass getSuperclass() {
+ if (mClass.getSuperclass() == null) {
+ return null;
+ }
+ return new JavaClass(mClass.getSuperclass());
+ }
+
+ @Override
+ public String getCanonicalName() {
+ return mClass.getCanonicalName();
+ }
+
+ @Override
+ public ModelClass erasure() {
+ return this;
+ }
+
+ @Override
+ public String getJniDescription() {
+ return TypeUtil.getInstance().getDescription(this);
+ }
+
+ @Override
+ protected ModelField[] getDeclaredFields() {
+ Field[] fields = mClass.getDeclaredFields();
+ ModelField[] modelFields;
+ if (fields == null) {
+ modelFields = new ModelField[0];
+ } else {
+ modelFields = new ModelField[fields.length];
+ for (int i = 0; i < fields.length; i++) {
+ modelFields[i] = new JavaField(fields[i]);
+ }
+ }
+ return modelFields;
+ }
+
+ @Override
+ protected ModelMethod[] getDeclaredMethods() {
+ Method[] methods = mClass.getDeclaredMethods();
+ if (methods == null) {
+ return new ModelMethod[0];
+ } else {
+ ModelMethod[] classMethods = new ModelMethod[methods.length];
+ for (int i = 0; i < methods.length; i++) {
+ classMethods[i] = new JavaMethod(methods[i]);
+ }
+ return classMethods;
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof JavaClass) {
+ return mClass.equals(((JavaClass) obj).mClass);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return mClass.hashCode();
+ }
+}
diff --git a/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaField.java b/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaField.java
new file mode 100644
index 0000000..6821f16
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaField.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.reflection.java;
+
+import android.databinding.Bindable;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.ModelField;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+public class JavaField extends ModelField {
+ public final Field mField;
+
+ public JavaField(Field field) {
+ mField = field;
+ }
+
+ @Override
+ public boolean isBindable() {
+ return mField.getAnnotation(Bindable.class) != null;
+ }
+
+ @Override
+ public String getName() {
+ return mField.getName();
+ }
+
+ @Override
+ public boolean isPublic() {
+ return Modifier.isPublic(mField.getModifiers());
+ }
+
+ @Override
+ public boolean isStatic() {
+ return Modifier.isStatic(mField.getModifiers());
+ }
+
+ @Override
+ public boolean isFinal() {
+ return Modifier.isFinal(mField.getModifiers());
+ }
+
+ @Override
+ public ModelClass getFieldType() {
+ return new JavaClass(mField.getType());
+ }
+}
diff --git a/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaMethod.java b/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaMethod.java
new file mode 100644
index 0000000..4ef566f
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaMethod.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.reflection.java;
+
+
+import android.databinding.Bindable;
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.ModelMethod;
+import android.databinding.tool.reflection.SdkUtil;
+import android.databinding.tool.reflection.TypeUtil;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.List;
+
+public class JavaMethod extends ModelMethod {
+ public final Method mMethod;
+
+ public JavaMethod(Method method) {
+ mMethod = method;
+ }
+
+
+ @Override
+ public ModelClass getDeclaringClass() {
+ return new JavaClass(mMethod.getDeclaringClass());
+ }
+
+ @Override
+ public ModelClass[] getParameterTypes() {
+ Class[] parameterTypes = mMethod.getParameterTypes();
+ ModelClass[] parameterClasses = new ModelClass[parameterTypes.length];
+ for (int i = 0; i < parameterTypes.length; i++) {
+ parameterClasses[i] = new JavaClass(parameterTypes[i]);
+ }
+ return parameterClasses;
+ }
+
+ @Override
+ public String getName() {
+ return mMethod.getName();
+ }
+
+ @Override
+ public ModelClass getReturnType(List<ModelClass> args) {
+ return new JavaClass(mMethod.getReturnType());
+ }
+
+ @Override
+ public boolean isVoid() {
+ return void.class.equals(mMethod.getReturnType());
+ }
+
+ @Override
+ public boolean isPublic() {
+ return Modifier.isPublic(mMethod.getModifiers());
+ }
+
+ @Override
+ public boolean isStatic() {
+ return Modifier.isStatic(mMethod.getModifiers());
+ }
+
+ @Override
+ public boolean isBindable() {
+ return mMethod.getAnnotation(Bindable.class) != null;
+ }
+
+ @Override
+ public int getMinApi() {
+ return SdkUtil.getMinApi(this);
+ }
+
+ @Override
+ public String getJniDescription() {
+ return TypeUtil.getInstance().getDescription(this);
+ }
+
+ @Override
+ public boolean isVarArgs() {
+ return false;
+ }
+}
diff --git a/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaTypeUtil.java b/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaTypeUtil.java
new file mode 100644
index 0000000..33bff3b
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/android/databinding/tool/reflection/java/JavaTypeUtil.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.reflection.java;
+
+import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.ModelMethod;
+import android.databinding.tool.reflection.TypeUtil;
+import android.databinding.tool.util.L;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+
+public class JavaTypeUtil extends TypeUtil {
+
+ @Override
+ public String getDescription(ModelClass modelClass) {
+ return modelClass.getCanonicalName().replace('.', '/');
+ }
+
+ @Override
+ public String getDescription(ModelMethod modelMethod) {
+ Method method = ((JavaMethod) modelMethod).mMethod;
+ StringBuilder sb = new StringBuilder();
+ sb.append(method.getName());
+ sb.append("(");
+ for (Class param : method.getParameterTypes()) {
+ sb.append(getDescription(param));
+ }
+ sb.append(")");
+ sb.append(getDescription(method.getReturnType()));
+ return sb.toString();
+ }
+
+ private String getDescription(Class klass) {
+ if (klass == null) {
+ throw new UnsupportedOperationException();
+ }
+ if (boolean.class.equals(klass)) {
+ return BOOLEAN;
+ }
+ if (byte.class.equals(klass)) {
+ return BYTE;
+ }
+ if (short.class.equals(klass)) {
+ return SHORT;
+ }
+ if (int.class.equals(klass)) {
+ return INT;
+ }
+ if (long.class.equals(klass)) {
+ return LONG;
+ }
+ if (char.class.equals(klass)) {
+ return CHAR;
+ }
+ if (float.class.equals(klass)) {
+ return FLOAT;
+ }
+ if (double.class.equals(klass)) {
+ return DOUBLE;
+ }
+ if (void.class.equals(klass)) {
+ return VOID;
+ }
+ if (Object.class.isAssignableFrom(klass)) {
+ return CLASS_PREFIX + klass.getCanonicalName().replace('.', '/') + CLASS_SUFFIX;
+ }
+ if (Array.class.isAssignableFrom(klass)) {
+ return ARRAY + getDescription(klass.getComponentType());
+ }
+
+ UnsupportedOperationException ex
+ = new UnsupportedOperationException("cannot understand type "
+ + klass.toString() + ", kind:");
+ L.e(ex, "cannot create JNI type for %s", klass.getCanonicalName());
+ throw ex;
+ }
+}
diff --git a/tools/data-binding/compiler/src/test/java/android/databinding/tool/writer/FlagSetTest.java b/tools/data-binding/compiler/src/test/java/android/databinding/tool/writer/FlagSetTest.java
new file mode 100644
index 0000000..327593a
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/android/databinding/tool/writer/FlagSetTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.writer;
+
+import org.junit.Test;
+
+import java.util.BitSet;
+
+import static org.junit.Assert.assertEquals;
+
+public class FlagSetTest {
+ @Test
+ public void testSimple1Level() {
+ BitSet bs = new BitSet();
+ bs.set(7);
+ FlagSet flagSet = new FlagSet(bs, 3);
+ assertEquals(3, flagSet.buckets.length);
+ assertEquals(1 << 7, flagSet.buckets[0]);
+ assertEquals(0, flagSet.buckets[1]);
+ assertEquals(0, flagSet.buckets[2]);
+ }
+
+ @Test
+ public void testSimple2Level() {
+ BitSet bs = new BitSet();
+ bs.set(FlagSet.sBucketSize + 2);
+ FlagSet flagSet = new FlagSet(bs, 3);
+ assertEquals(3, flagSet.buckets.length);
+ assertEquals(0, flagSet.buckets[0]);
+ assertEquals(1 << 2, flagSet.buckets[1]);
+ assertEquals(0, flagSet.buckets[2]);
+ }
+
+ @Test
+ public void testSimple3Level() {
+ BitSet bs = new BitSet();
+ bs.set(5);
+ bs.set(FlagSet.sBucketSize + 2);
+ bs.set(FlagSet.sBucketSize * 2 + 10);
+ FlagSet flagSet = new FlagSet(bs, 3);
+ assertEquals(3, flagSet.buckets.length);
+ assertEquals(1 << 5, flagSet.buckets[0]);
+ assertEquals(1 << 2, flagSet.buckets[1]);
+ assertEquals(1 << 10, flagSet.buckets[2]);
+ }
+}
diff --git a/tools/data-binding/databinding.properties b/tools/data-binding/databinding.properties
new file mode 100644
index 0000000..0bda1db
--- /dev/null
+++ b/tools/data-binding/databinding.properties
@@ -0,0 +1,11 @@
+# global settings for projects
+kotlinVersion = 0.11.91
+releaseVersion = 0.3
+snapshotVersion = 0.3-SNAPSHOT
+androidPluginVersion = 1.0.1
+javaTargetCompatibility = 1.6
+javaSourceCompatibility = 1.6
+mavenRepoName=maven-repo
+group=com.android.databinding
+testGroup=com.android.databinding.test
+
diff --git a/tools/data-binding/extensions/baseAdapters/build.gradle b/tools/data-binding/extensions/baseAdapters/build.gradle
new file mode 100644
index 0000000..34ca4b9
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/build.gradle
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+
+apply plugin: 'maven'
+apply plugin: 'com.android.library'
+apply plugin: 'com.android.databinding'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ minSdkVersion 7
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ packagingOptions {
+ exclude 'META-INF/services/javax.annotation.processing.Processor'
+ exclude 'META-INF/LICENSE.txt'
+ exclude 'META-INF/NOTICE.txt'
+ }
+}
+
+dependencies {
+ compile "com.android.databinding:baseLibrary:${config.snapshotVersion}"
+ provided "com.android.databinding:annotationprocessor:${config.snapshotVersion}"
+ compile 'com.android.support:support-v4:+'
+ compile 'com.android.support:cardview-v7:+'
+ compile 'com.android.support:appcompat-v7:+'
+}
+
+configurations {
+ jarArchives
+}
+
+
+//create jar tasks
+android.libraryVariants.all { variant ->
+ def name = variant.buildType.name
+
+ if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
+ return; // Skip debug builds.
+ }
+ // @Jar version is needed to run compiler tests
+ def task = project.tasks.create "jar${name.capitalize()}", Jar
+ task.dependsOn variant.javaCompile
+ task.from variant.javaCompile.destinationDir
+ def packageName = "com.android.databinding.library.baseAdapters"
+ def appPkgAsClass = packageName.replace('.', '/')
+ task.exclude("android/databinding/layouts/*.*")
+ task.exclude("$appPkgAsClass/databinding/*")
+ task.exclude("$appPkgAsClass/BR.*")
+ artifacts.add('jarArchives', task);
+}
+
+uploadArchives {
+}
+
+uploadJarArchives {
+ repositories {
+ mavenDeployer {
+ repository(url: "file://${config.mavenRepoDir}")
+ pom.artifactId = "adapters"
+ pom.whenConfigured {
+ println("configured pom, $it")
+ it.dependencies.find {dep -> dep.groupId == 'com.android.support' && dep.artifactId == 'support-v4' }.optional = true
+ it.dependencies.find {dep -> dep.groupId == 'com.android.support' && dep.artifactId == 'cardview-v7' }.optional = true
+ it.dependencies.find {dep -> dep.groupId == 'com.android.support' && dep.artifactId == 'appcompat-v7' }.optional = true
+ }
+ }
+ }
+}
+
+uploadArchives.dependsOn uploadJarArchives
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/AndroidManifest.xml b/tools/data-binding/extensions/baseAdapters/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..38cf779
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.databinding.library.baseAdapters">
+</manifest>
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/AbsListViewBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/AbsListViewBindingAdapter.java
new file mode 100644
index 0000000..e645bff
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/AbsListViewBindingAdapter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+
+@BindingMethods({
+ @BindingMethod(type = "android.widget.AbsListView", attribute = "android:listSelector", method = "setSelector"),
+ @BindingMethod(type = "android.widget.AbsListView", attribute = "android:scrollingCache", method = "setScrollingCacheEnabled"),
+ @BindingMethod(type = "android.widget.AbsListView", attribute = "android:smoothScrollbar", method = "setSmoothScrollbarEnabled"),
+})
+public class AbsListViewBindingAdapter {
+
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/AbsSeekBarBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/AbsSeekBarBindingAdapter.java
new file mode 100644
index 0000000..4494ec7
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/AbsSeekBarBindingAdapter.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+
+@BindingMethods({
+ @BindingMethod(type = "android.widget.AbsSeekBar", attribute = "android:thumbTint", method = "setThumbTintList"),
+
+})
+public class AbsSeekBarBindingAdapter {
+
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/AbsSpinnerBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/AbsSpinnerBindingAdapter.java
new file mode 100644
index 0000000..adf84b2
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/AbsSpinnerBindingAdapter.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingAdapter;
+import android.widget.AbsSpinner;
+import android.widget.ArrayAdapter;
+import android.widget.SpinnerAdapter;
+
+public class AbsSpinnerBindingAdapter {
+
+ @BindingAdapter("android:entries")
+ public static void setEntries(AbsSpinner view, CharSequence[] entries) {
+ if (entries != null) {
+ SpinnerAdapter oldAdapter = view.getAdapter();
+ boolean changed = true;
+ if (oldAdapter != null && oldAdapter.getCount() == entries.length) {
+ changed = false;
+ for (int i = 0; i < entries.length; i++) {
+ if (!entries[i].equals(oldAdapter.getItem(i))) {
+ changed = true;
+ break;
+ }
+ }
+ }
+ if (changed) {
+ ArrayAdapter<CharSequence> adapter =
+ new ArrayAdapter<CharSequence>(view.getContext(),
+ android.R.layout.simple_spinner_item, entries);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ view.setAdapter(adapter);
+ }
+ } else {
+ view.setAdapter(null);
+ }
+ }
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/AutoCompleteTextViewBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/AutoCompleteTextViewBindingAdapter.java
new file mode 100644
index 0000000..334d818
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/AutoCompleteTextViewBindingAdapter.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+
+@BindingMethods({
+ @BindingMethod(type = "android.widget.AutoCompleteTextView", attribute = "android:completionThreshold", method = "setThreshold"),
+ @BindingMethod(type = "android.widget.AutoCompleteTextView", attribute = "android:popupBackground", method = "setDropDownBackgroundDrawable"),
+})
+public class AutoCompleteTextViewBindingAdapter {
+
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/CardViewBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/CardViewBindingAdapter.java
new file mode 100644
index 0000000..0545141
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/CardViewBindingAdapter.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingAdapter;
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+import android.support.v7.widget.CardView;
+
+@BindingMethods({
+ @BindingMethod(type = "android.support.v7.widget.CardView", attribute = "cardCornerRadius", method = "setRadius"),
+ @BindingMethod(type = "android.support.v7.widget.CardView", attribute = "cardMaxElevation", method = "setMaxCardElevation"),
+ @BindingMethod(type = "android.support.v7.widget.CardView", attribute = "cardPreventCornerOverlap", method = "setPreventCornerOverlap"),
+ @BindingMethod(type = "android.support.v7.widget.CardView", attribute = "cardUseCompatPadding", method = "setUseCompatPadding"),
+})
+public class CardViewBindingAdapter {
+
+ @BindingAdapter("contentPadding")
+ public static void setContentPadding(CardView view, int padding) {
+ view.setContentPadding(padding, padding, padding, padding);
+ }
+
+ @BindingAdapter("contentPaddingLeft")
+ public static void setContentPaddingLeft(CardView view, int left) {
+ int top = view.getContentPaddingTop();
+ int right = view.getContentPaddingRight();
+ int bottom = view.getContentPaddingBottom();
+ view.setContentPadding(left, top, right, bottom);
+ }
+
+ @BindingAdapter("contentPaddingTop")
+ public static void setContentPaddingTop(CardView view, int top) {
+ int left = view.getContentPaddingLeft();
+ int right = view.getContentPaddingRight();
+ int bottom = view.getContentPaddingBottom();
+ view.setContentPadding(left, top, right, bottom);
+ }
+
+ @BindingAdapter("contentPaddingRight")
+ public static void setContentPaddingRight(CardView view, int right) {
+ int left = view.getContentPaddingLeft();
+ int top = view.getContentPaddingTop();
+ int bottom = view.getContentPaddingBottom();
+ view.setContentPadding(left, top, right, bottom);
+ }
+
+ @BindingAdapter("contentPaddingBottom")
+ public static void setContentPaddingBottom(CardView view, int bottom) {
+ int left = view.getContentPaddingLeft();
+ int top = view.getContentPaddingTop();
+ int right = view.getContentPaddingRight();
+ view.setContentPadding(left, top, right, bottom);
+ }
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/CheckedTextViewBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/CheckedTextViewBindingAdapter.java
new file mode 100644
index 0000000..aa16057
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/CheckedTextViewBindingAdapter.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+
+@BindingMethods({
+ @BindingMethod(type = "android.widget.CheckedTextView", attribute = "android:checkMark", method = "setCheckMarkDrawable"),
+ @BindingMethod(type = "android.widget.CheckedTextView", attribute = "android:checkMarkTint", method = "setCheckMarkTintList"),
+})
+public class CheckedTextViewBindingAdapter {
+
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/CompoundButtonBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/CompoundButtonBindingAdapter.java
new file mode 100644
index 0000000..9ed5dd7
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/CompoundButtonBindingAdapter.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+
+@BindingMethods({
+ @BindingMethod(type = "android.widget.CompoundButton", attribute = "android:buttonTint", method = "setButtonTintList"),
+})
+public class CompoundButtonBindingAdapter {
+
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/Converters.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/Converters.java
new file mode 100644
index 0000000..44eda4d
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/Converters.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingConversion;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.ColorDrawable;
+
+public class Converters {
+ @BindingConversion
+ public static ColorDrawable convertColorToDrawable(int color) {
+ return new ColorDrawable(color);
+ }
+
+ @BindingConversion
+ public static ColorStateList convertColorToColorStateList(int color) {
+ return ColorStateList.valueOf(color);
+ }
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/FrameLayoutBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/FrameLayoutBindingAdapter.java
new file mode 100644
index 0000000..82641a6
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/FrameLayoutBindingAdapter.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+
+@BindingMethods({
+ @BindingMethod(type = "android.widget.FrameLayout", attribute = "android:foregroundTint", method = "setForegroundTintList"),
+})
+public class FrameLayoutBindingAdapter {
+
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ImageViewBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ImageViewBindingAdapter.java
new file mode 100644
index 0000000..9be1a14
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ImageViewBindingAdapter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+
+@BindingMethods({
+ @BindingMethod(type = "android.widget.ImageView", attribute = "android:src", method = "setImageDrawable"),
+ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tint", method = "setImageTintList"),
+ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tintMode", method = "setImageTintMode"),
+})
+public class ImageViewBindingAdapter {
+
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/LinearLayoutBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/LinearLayoutBindingAdapter.java
new file mode 100644
index 0000000..7bb85e9
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/LinearLayoutBindingAdapter.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+
+@BindingMethods({
+ @BindingMethod(type = "android.widget.LinearLayout", attribute = "android:divider", method = "setDividerDrawable"),
+ @BindingMethod(type = "android.widget.LinearLayout", attribute = "android:measureWithLargestChild", method = "setMeasureWithLargestChildEnabled"),
+})
+public class LinearLayoutBindingAdapter {
+
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ProgressBarBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ProgressBarBindingAdapter.java
new file mode 100644
index 0000000..fdbab8f
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ProgressBarBindingAdapter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+
+@BindingMethods({
+ @BindingMethod(type = "android.widget.ProgressBar", attribute = "android:indeterminateTint", method = "setIndeterminateTintList"),
+ @BindingMethod(type = "android.widget.ProgressBar", attribute = "android:progressTint", method = "setProgressTintList"),
+ @BindingMethod(type = "android.widget.ProgressBar", attribute = "android:secondaryProgressTint", method = "setSecondaryProgressTintList"),
+})
+public class ProgressBarBindingAdapter {
+
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/RadioGroupBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/RadioGroupBindingAdapter.java
new file mode 100644
index 0000000..727fecf
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/RadioGroupBindingAdapter.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+
+@BindingMethods({
+ @BindingMethod(type = "android.widget.RadioGroup", attribute = "android:checkedButton", method = "check"),
+})
+public class RadioGroupBindingAdapter {
+
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/SpinnerBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/SpinnerBindingAdapter.java
new file mode 100644
index 0000000..eb98629
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/SpinnerBindingAdapter.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+
+@BindingMethods({
+ @BindingMethod(type = "android.widget.Spinner", attribute = "android:popupBackground", method = "setPopupBackgroundDrawable"),
+})
+public class SpinnerBindingAdapter {
+
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/SwitchBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/SwitchBindingAdapter.java
new file mode 100644
index 0000000..05307c7
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/SwitchBindingAdapter.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.annotation.TargetApi;
+import android.databinding.BindingAdapter;
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+import android.os.Build;
+import android.widget.Switch;
+
+@BindingMethods({
+ @BindingMethod(type = "android.widget.Switch", attribute = "android:thumb", method = "setThumbDrawable"),
+ @BindingMethod(type = "android.widget.Switch", attribute = "android:track", method = "setTrackDrawable"),
+})
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class SwitchBindingAdapter {
+
+ @BindingAdapter("android:switchTextAppearance")
+ public static void setSwitchTextAppearance(Switch view, int value) {
+ view.setSwitchTextAppearance(null, value);
+ }
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/SwitchCompatBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/SwitchCompatBindingAdapter.java
new file mode 100644
index 0000000..8dca9ac
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/SwitchCompatBindingAdapter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingAdapter;
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+import android.support.v7.widget.SwitchCompat;
+
+@BindingMethods({
+ @BindingMethod(type = "android.support.v7.widget.SwitchCompat", attribute = "android:thumb", method = "setThumbDrawable"),
+ @BindingMethod(type = "android.support.v7.widget.SwitchCompat", attribute = "android:track", method = "setTrackDrawable"),
+})
+public class SwitchCompatBindingAdapter {
+
+ @BindingAdapter("android:switchTextAppearance")
+ public static void setSwitchTextAppearance(SwitchCompat view, int value) {
+ view.setSwitchTextAppearance(null, value);
+ }
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/TabWidgetBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/TabWidgetBindingAdapter.java
new file mode 100644
index 0000000..c5fec7f
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/TabWidgetBindingAdapter.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+
+@BindingMethods({
+ @BindingMethod(type = "android.widget.TabWidget", attribute = "android:divider", method = "setDividerDrawable"),
+ @BindingMethod(type = "android.widget.TabWidget", attribute = "android:tabStripEnabled", method = "setStripEnabled"),
+ @BindingMethod(type = "android.widget.TabWidget", attribute = "android:tabStripLeft", method = "setLeftStripDrawable"),
+ @BindingMethod(type = "android.widget.TabWidget", attribute = "android:tabStripRight", method = "setRightStripDrawable"),
+})
+public class TabWidgetBindingAdapter {
+
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/TableLayoutBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/TableLayoutBindingAdapter.java
new file mode 100644
index 0000000..e0c7591
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/TableLayoutBindingAdapter.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingAdapter;
+import android.util.SparseBooleanArray;
+import android.widget.TableLayout;
+
+import java.util.regex.Pattern;
+
+public class TableLayoutBindingAdapter {
+
+ private static Pattern sColumnPattern = Pattern.compile("\\s*,\\s*");
+
+ private static final int MAX_COLUMNS = 20;
+
+ @BindingAdapter("android:collapseColumns")
+ public static void setCollapseColumns(TableLayout view, CharSequence columnsStr) {
+ SparseBooleanArray columns = parseColumns(columnsStr);
+ for (int i = 0; i < MAX_COLUMNS; i++) {
+ boolean isCollapsed = columns.get(i, false);
+ if (isCollapsed != view.isColumnCollapsed(i)) {
+ view.setColumnCollapsed(i, isCollapsed);
+ }
+ }
+ }
+
+ @BindingAdapter("android:shrinkColumns")
+ public static void setShrinkColumns(TableLayout view, CharSequence columnsStr) {
+ if (columnsStr != null && columnsStr.length() > 0 && columnsStr.charAt(0) == '*') {
+ view.setShrinkAllColumns(true);
+ } else {
+ view.setShrinkAllColumns(false);
+ SparseBooleanArray columns = parseColumns(columnsStr);
+ int columnCount = columns.size();
+ for (int i = 0; i < columnCount; i++) {
+ int column = columns.keyAt(i);
+ boolean shrinkable = columns.valueAt(i);
+ if (shrinkable) {
+ view.setColumnShrinkable(column, shrinkable);
+ }
+ }
+ }
+ }
+
+ @BindingAdapter("android:stretchColumns")
+ public static void setStretchColumns(TableLayout view, CharSequence columnsStr) {
+ if (columnsStr != null && columnsStr.length() > 0 && columnsStr.charAt(0) == '*') {
+ view.setStretchAllColumns(true);
+ } else {
+ view.setStretchAllColumns(false);
+ SparseBooleanArray columns = parseColumns(columnsStr);
+ int columnCount = columns.size();
+ for (int i = 0; i < columnCount; i++) {
+ int column = columns.keyAt(i);
+ boolean stretchable = columns.valueAt(i);
+ if (stretchable) {
+ view.setColumnStretchable(column, stretchable);
+ }
+ }
+ }
+ }
+
+ private static SparseBooleanArray parseColumns(CharSequence sequence) {
+ SparseBooleanArray columns = new SparseBooleanArray();
+ if (sequence == null) {
+ return columns;
+ }
+ String[] columnDefs = sColumnPattern.split(sequence);
+
+ for (String columnIdentifier : columnDefs) {
+ try {
+ int columnIndex = Integer.parseInt(columnIdentifier);
+ // only valid, i.e. positive, columns indexes are handled
+ if (columnIndex >= 0) {
+ // putting true in this sparse array indicates that the
+ // column index was defined in the XML file
+ columns.put(columnIndex, true);
+ }
+ } catch (NumberFormatException e) {
+ // we just ignore columns that don't exist
+ }
+ }
+
+ return columns;
+ }
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/TextViewBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/TextViewBindingAdapter.java
new file mode 100644
index 0000000..3683916
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/TextViewBindingAdapter.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingAdapter;
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.text.InputFilter;
+import android.text.InputType;
+import android.text.method.DialerKeyListener;
+import android.text.method.DigitsKeyListener;
+import android.text.method.KeyListener;
+import android.text.method.PasswordTransformationMethod;
+import android.text.method.TextKeyListener;
+import android.util.Log;
+import android.util.TypedValue;
+import android.widget.TextView;
+
+@BindingMethods({
+ @BindingMethod(type = "android.widget.TextView", attribute = "android:autoLink", method = "setAutoLinkMask"),
+ @BindingMethod(type = "android.widget.TextView", attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"),
+ @BindingMethod(type = "android.widget.TextView", attribute = "android:editorExtras", method = "setInputExtras"),
+ @BindingMethod(type = "android.widget.TextView", attribute = "android:inputType", method = "setRawInputType"),
+ @BindingMethod(type = "android.widget.TextView", attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"),
+ @BindingMethod(type = "android.widget.TextView", attribute = "android:textAllCaps", method = "setAllCaps"),
+ @BindingMethod(type = "android.widget.TextView", attribute = "android:textColorHighlight", method = "setHighlightColor"),
+ @BindingMethod(type = "android.widget.TextView", attribute = "android:textColorHint", method = "setHintTextColor"),
+ @BindingMethod(type = "android.widget.TextView", attribute = "android:textColorLink", method = "setLinkTextColor"),
+})
+public class TextViewBindingAdapter {
+
+ private static final String TAG = "TextViewBindingAdapters";
+
+ public static final int INTEGER = 0x01;
+
+ public static final int SIGNED = 0x03;
+
+ public static final int DECIMAL = 0x05;
+
+ @BindingAdapter("android:autoText")
+ public static void setAutoText(TextView view, boolean autoText) {
+ KeyListener listener = view.getKeyListener();
+
+ TextKeyListener.Capitalize capitalize = TextKeyListener.Capitalize.NONE;
+
+ int inputType = listener != null ? listener.getInputType() : 0;
+ if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
+ capitalize = TextKeyListener.Capitalize.CHARACTERS;
+ } else if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
+ capitalize = TextKeyListener.Capitalize.WORDS;
+ } else if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
+ capitalize = TextKeyListener.Capitalize.SENTENCES;
+ }
+ view.setKeyListener(TextKeyListener.getInstance(autoText, capitalize));
+ }
+
+ @BindingAdapter("android:capitalize")
+ public static void setCapitalize(TextView view, TextKeyListener.Capitalize capitalize) {
+ KeyListener listener = view.getKeyListener();
+
+ int inputType = listener.getInputType();
+ boolean autoText = (inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
+ view.setKeyListener(TextKeyListener.getInstance(autoText, capitalize));
+ }
+
+ @BindingAdapter("android:bufferType")
+ public static void setBufferType(TextView view, TextView.BufferType bufferType) {
+ view.setText(view.getText(), bufferType);
+ }
+
+ @BindingAdapter("android:digits")
+ public static void setDigits(TextView view, CharSequence digits) {
+ if (digits != null) {
+ view.setKeyListener(DigitsKeyListener.getInstance(digits.toString()));
+ } else if (view.getKeyListener() instanceof DigitsKeyListener) {
+ view.setKeyListener(null);
+ }
+ }
+
+ @BindingAdapter("android:numeric")
+ public static void setNumeric(TextView view, int numeric) {
+ view.setKeyListener(DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
+ (numeric & DECIMAL) != 0));
+ }
+
+ @BindingAdapter("android:phoneNumber")
+ public static void setPhoneNumber(TextView view, boolean phoneNumber) {
+ if (phoneNumber) {
+ view.setKeyListener(DialerKeyListener.getInstance());
+ } else if (view.getKeyListener() instanceof DialerKeyListener) {
+ view.setKeyListener(null);
+ }
+ }
+
+ @BindingAdapter("android:drawableBottom")
+ public static void setDrawableBottom(TextView view, Drawable drawable) {
+ Drawable[] drawables = view.getCompoundDrawables();
+ view.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawable);
+ }
+
+ @BindingAdapter("android:drawableLeft")
+ public static void setDrawableLeft(TextView view, Drawable drawable) {
+ Drawable[] drawables = view.getCompoundDrawables();
+ view.setCompoundDrawables(drawable, drawables[1], drawables[2], drawables[3]);
+ }
+
+ @BindingAdapter("android:drawableRight")
+ public static void setDrawableRight(TextView view, Drawable drawable) {
+ Drawable[] drawables = view.getCompoundDrawables();
+ view.setCompoundDrawables(drawables[0], drawables[1], drawable, drawables[3]);
+ }
+
+ @BindingAdapter("android:drawableTop")
+ public static void setDrawableTop(TextView view, Drawable drawable) {
+ Drawable[] drawables = view.getCompoundDrawables();
+ view.setCompoundDrawables(drawables[0], drawable, drawables[2], drawables[3]);
+ }
+
+ @BindingAdapter("android:drawableStart")
+ public static void setDrawableStart(TextView view, Drawable drawable) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ setDrawableLeft(view, drawable);
+ } else {
+ Drawable[] drawables = view.getCompoundDrawablesRelative();
+ view.setCompoundDrawablesRelative(drawable, drawables[1], drawables[2], drawables[3]);
+ }
+ }
+
+ @BindingAdapter("android:drawableEnd")
+ public static void setDrawableEnd(TextView view, Drawable drawable) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ setDrawableRight(view, drawable);
+ } else {
+ Drawable[] drawables = view.getCompoundDrawablesRelative();
+ view.setCompoundDrawablesRelative(drawables[0], drawables[1], drawable, drawables[3]);
+ }
+ }
+
+ @BindingAdapter("android:imeActionLabel")
+ public static void setImeActionLabel(TextView view, CharSequence value) {
+ view.setImeActionLabel(value, view.getImeActionId());
+ }
+
+ @BindingAdapter("android:imeActionId")
+ public static void setImeActionLabel(TextView view, int value) {
+ view.setImeActionLabel(view.getImeActionLabel(), value);
+ }
+
+ @BindingAdapter("android:inputMethod")
+ public static void setInputMethod(TextView view, CharSequence inputMethod) {
+ try {
+ Class<?> c = Class.forName(inputMethod.toString());
+ view.setKeyListener((KeyListener) c.newInstance());
+ } catch (ClassNotFoundException e) {
+ Log.e(TAG, "Could not create input method: " + inputMethod, e);
+ } catch (InstantiationException e) {
+ Log.e(TAG, "Could not create input method: " + inputMethod, e);
+ } catch (IllegalAccessException e) {
+ Log.e(TAG, "Could not create input method: " + inputMethod, e);
+ }
+ }
+
+ @BindingAdapter("android:lineSpacingExtra")
+ public static void setLineSpacingExtra(TextView view, float value) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ view.setLineSpacing(value, view.getLineSpacingMultiplier());
+ } else {
+ view.setLineSpacing(value, 1);
+ }
+ }
+
+ @BindingAdapter("android:lineSpacingMultiplier")
+ public static void setLineSpacingMultiplier(TextView view, float value) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ view.setLineSpacing(view.getLineSpacingExtra(), value);
+ } else {
+ view.setLineSpacing(0, value);
+ }
+ }
+
+ @BindingAdapter("android:maxLength")
+ public static void setMaxLength(TextView view, int value) {
+ InputFilter[] filters = view.getFilters();
+ if (filters == null) {
+ filters = new InputFilter[]{
+ new InputFilter.LengthFilter(value)
+ };
+ } else {
+ boolean foundMaxLength = false;
+ for (int i = 0; i < filters.length; i++) {
+ InputFilter filter = filters[i];
+ if (filter instanceof InputFilter.LengthFilter) {
+ foundMaxLength = true;
+ boolean replace = true;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ replace = ((InputFilter.LengthFilter) filter).getMax() != value;
+ }
+ if (replace) {
+ filters[i] = new InputFilter.LengthFilter(value);
+ }
+ break;
+ }
+ }
+ if (!foundMaxLength) {
+ // can't use Arrays.copyOf -- it shows up in API 9
+ InputFilter[] oldFilters = filters;
+ filters = new InputFilter[oldFilters.length + 1];
+ System.arraycopy(oldFilters, 0, filters, 0, oldFilters.length);
+ filters[filters.length - 1] = new InputFilter.LengthFilter(value);
+ }
+ }
+ view.setFilters(filters);
+ }
+
+ @BindingAdapter("android:password")
+ public static void setPassword(TextView view, boolean password) {
+ if (password) {
+ view.setTransformationMethod(PasswordTransformationMethod.getInstance());
+ } else if (view.getTransformationMethod() instanceof PasswordTransformationMethod) {
+ view.setTransformationMethod(null);
+ }
+ }
+
+ @BindingAdapter("android:shadowColor")
+ public static void setShadowColor(TextView view, int color) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ float dx = view.getShadowDx();
+ float dy = view.getShadowDy();
+ float r = view.getShadowRadius();
+ view.setShadowLayer(r, dx, dy, color);
+ }
+ }
+
+ @BindingAdapter("android:shadowDx")
+ public static void setShadowDx(TextView view, float dx) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ int color = view.getShadowColor();
+ float dy = view.getShadowDy();
+ float r = view.getShadowRadius();
+ view.setShadowLayer(r, dx, dy, color);
+ }
+ }
+
+ @BindingAdapter("android:shadowDy")
+ public static void setShadowDy(TextView view, float dy) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ int color = view.getShadowColor();
+ float dx = view.getShadowDx();
+ float r = view.getShadowRadius();
+ view.setShadowLayer(r, dx, dy, color);
+ }
+ }
+
+ @BindingAdapter("android:shadowRadius")
+ public static void setShadowRadius(TextView view, float r) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ int color = view.getShadowColor();
+ float dx = view.getShadowDx();
+ float dy = view.getShadowDy();
+ view.setShadowLayer(r, dx, dy, color);
+ }
+ }
+
+ @BindingAdapter("android:textSize")
+ public static void setTextSize(TextView view, float size) {
+ view.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
+ }
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ViewBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ViewBindingAdapter.java
new file mode 100644
index 0000000..e8b9e43
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ViewBindingAdapter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingAdapter;
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+import android.os.Build;
+import android.view.View;
+
+@BindingMethods({
+ @BindingMethod(type = "android.view.View", attribute = "android:backgroundTint", method = "setBackgroundTintList"),
+ @BindingMethod(type = "android.view.View", attribute = "android:fadeScrollbars", method = "setScrollbarFadingEnabled"),
+ @BindingMethod(type = "android.view.View", attribute = "android:nextFocusForward", method = "setNextFocusForwardId"),
+ @BindingMethod(type = "android.view.View", attribute = "android:nextFocusLeft", method = "setNextFocusLeftId"),
+ @BindingMethod(type = "android.view.View", attribute = "android:nextFocusRight", method = "setNextFocusRightId"),
+ @BindingMethod(type = "android.view.View", attribute = "android:nextFocusUp", method = "setNextFocusUpId"),
+ @BindingMethod(type = "android.view.View", attribute = "android:nextFocusDown", method = "setNextFocusDownId"),
+ @BindingMethod(type = "android.view.View", attribute = "android:requiresFadingEdge", method = "setVerticalFadingEdgeEnabled"),
+ @BindingMethod(type = "android.view.View", attribute = "android:scrollbarDefaultDelayBeforeFade", method = "setScrollBarDefaultDelayBeforeFade"),
+ @BindingMethod(type = "android.view.View", attribute = "android:scrollbarFadeDuration", method = "setScrollBarFadeDuration"),
+ @BindingMethod(type = "android.view.View", attribute = "android:scrollbarSize", method = "setScrollBarSize"),
+ @BindingMethod(type = "android.view.View", attribute = "android:scrollbarStyle", method = "setScrollBarStyle"),
+ @BindingMethod(type = "android.view.View", attribute = "android:transformPivotX", method = "setPivotX"),
+ @BindingMethod(type = "android.view.View", attribute = "android:transformPivotY", method = "setPivotY"),
+})
+public class ViewBindingAdapter {
+ public static int FADING_EDGE_NONE = 0;
+ public static int FADING_EDGE_HORIZONTAL = 1;
+ public static int FADING_EDGE_VERTICAL = 2;
+
+ @BindingAdapter("android:padding")
+ public static void setPadding(View view, int padding) {
+ view.setPadding(padding, padding, padding, padding);
+ }
+
+ @BindingAdapter("android:paddingBottom")
+ public static void setPaddingBottom(View view, int padding) {
+ view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(),
+ padding);
+ }
+
+ @BindingAdapter("android:paddingEnd")
+ public static void setPaddingEnd(View view, int padding) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ view.setPaddingRelative(view.getPaddingStart(), view.getPaddingTop(), padding,
+ view.getPaddingBottom());
+ } else {
+ view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), padding,
+ view.getPaddingBottom());
+ }
+ }
+
+ @BindingAdapter("android:paddingLeft")
+ public static void setPaddingLeft(View view, int padding) {
+ view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(),
+ view.getPaddingBottom());
+ }
+
+ @BindingAdapter("android:paddingRight")
+ public static void setPaddingRight(View view, int padding) {
+ view.setPadding(view.getPaddingLeft(), view.getPaddingTop(), padding,
+ view.getPaddingBottom());
+ }
+
+ @BindingAdapter("android:paddingStart")
+ public static void setPaddingStart(View view, int padding) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ view.setPaddingRelative(padding, view.getPaddingTop(), view.getPaddingEnd(),
+ view.getPaddingBottom());
+ } else {
+ view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(),
+ view.getPaddingBottom());
+ }
+ }
+
+ @BindingAdapter("android:paddingTop")
+ public static void setPaddingTop(View view, int padding) {
+ view.setPadding(view.getPaddingLeft(), padding, view.getPaddingRight(),
+ view.getPaddingBottom());
+ }
+
+ @BindingAdapter("android:requiresFadingEdge")
+ public static void setRequiresFadingEdge(View view, int value) {
+ final boolean vertical = (value & FADING_EDGE_VERTICAL) != 0;
+ final boolean horizontal = (value & FADING_EDGE_HORIZONTAL) != 0;
+ view.setVerticalFadingEdgeEnabled(vertical);
+ view.setHorizontalFadingEdgeEnabled(horizontal);
+ }
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ViewGroupBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ViewGroupBindingAdapter.java
new file mode 100644
index 0000000..aaad4d1
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ViewGroupBindingAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.animation.LayoutTransition;
+import android.annotation.TargetApi;
+import android.databinding.BindingAdapter;
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+import android.os.Build;
+import android.view.ViewGroup;
+
+@BindingMethods({
+ @BindingMethod(type = "android.view.ViewGroup", attribute = "android:alwaysDrawnWithCache", method = "setAlwaysDrawnWithCacheEnabled"),
+ @BindingMethod(type = "android.view.ViewGroup", attribute = "android:animationCache", method = "setAnimationCacheEnabled"),
+ @BindingMethod(type = "android.view.ViewGroup", attribute = "android:splitMotionEvents", method = "setMotionEventSplittingEnabled"),
+})
+public class ViewGroupBindingAdapter {
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @BindingAdapter("android:animateLayoutChanges")
+ public static void setAnimateLayoutChanges(ViewGroup view, boolean animate) {
+ if (animate) {
+ view.setLayoutTransition(new LayoutTransition());
+ } else {
+ view.setLayoutTransition(null);
+ }
+ }
+}
diff --git a/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ViewStubBindingAdapter.java b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ViewStubBindingAdapter.java
new file mode 100644
index 0000000..37864cc
--- /dev/null
+++ b/tools/data-binding/extensions/baseAdapters/src/main/java/android/databinding/adapters/ViewStubBindingAdapter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.adapters;
+
+import android.databinding.BindingMethod;
+import android.databinding.BindingMethods;
+import android.databinding.Untaggable;
+
+@Untaggable({"android.view.ViewStub"})
+@BindingMethods({
+ @BindingMethod(type = "android.view.ViewStub", attribute = "android:layout", method = "setLayoutResource")
+})
+public class ViewStubBindingAdapter {
+
+}
diff --git a/tools/data-binding/extensions/build.gradle b/tools/data-binding/extensions/build.gradle
new file mode 100644
index 0000000..cfa2697
--- /dev/null
+++ b/tools/data-binding/extensions/build.gradle
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+
+buildscript {
+ def Properties dataBindingProperties = new Properties()
+ dataBindingProperties.load(new FileInputStream("${projectDir}/../databinding.properties"))
+ dataBindingProperties.mavenRepoDir = "${projectDir}/../${dataBindingProperties.mavenRepoName}"
+ ext.config = dataBindingProperties
+ repositories {
+ jcenter()
+ maven {
+ url config.mavenRepoDir
+ }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.1.3'
+ classpath "com.android.databinding:dataBinder:${config.snapshotVersion}"
+ }
+}
+
+subprojects {
+ apply plugin: 'maven'
+ group = config.group
+ version = config.snapshotVersion
+ repositories {
+ mavenCentral()
+ maven {
+ url config.mavenRepoDir
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/data-binding/extensions/gradle/wrapper/gradle-wrapper.jar b/tools/data-binding/extensions/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/tools/data-binding/extensions/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/tools/data-binding/extensions/gradle/wrapper/gradle-wrapper.properties b/tools/data-binding/extensions/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e5fd879
--- /dev/null
+++ b/tools/data-binding/extensions/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Mar 12 15:27:48 PDT 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/tools/data-binding/extensions/gradlew b/tools/data-binding/extensions/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/tools/data-binding/extensions/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/tools/data-binding/extensions/settings.gradle b/tools/data-binding/extensions/settings.gradle
new file mode 100644
index 0000000..80ebcc8
--- /dev/null
+++ b/tools/data-binding/extensions/settings.gradle
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * These are projects that requires a compiled version of data binding.
+ */
+include ':baseAdapters'
diff --git a/tools/data-binding/gradle.properties b/tools/data-binding/gradle.properties
new file mode 100644
index 0000000..24f728c
--- /dev/null
+++ b/tools/data-binding/gradle.properties
@@ -0,0 +1,35 @@
+#
+# Copyright (C) 2014 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.
+#
+
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=1024m -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+#org.gradle.parallel=true
+org.gradle.daemon=true
diff --git a/tools/data-binding/gradle/wrapper/gradle-wrapper.jar b/tools/data-binding/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/tools/data-binding/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/tools/data-binding/gradle/wrapper/gradle-wrapper.properties b/tools/data-binding/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e5fd879
--- /dev/null
+++ b/tools/data-binding/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Mar 12 15:27:48 PDT 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/tools/data-binding/gradlePlugin/build.gradle b/tools/data-binding/gradlePlugin/build.gradle
new file mode 100644
index 0000000..bdfdf79
--- /dev/null
+++ b/tools/data-binding/gradlePlugin/build.gradle
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+apply plugin: 'java'
+apply plugin: "kotlin"
+apply plugin: 'maven'
+
+sourceCompatibility = config.javaTargetCompatibility
+targetCompatibility = config.javaSourceCompatibility
+
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${config.kotlinVersion}"
+ }
+}
+
+dependencies {
+ compile "com.android.tools.build:gradle:${config.androidPluginVersion}"
+ compile "org.jetbrains.kotlin:kotlin-stdlib:${config.kotlinVersion}"
+ compile gradleApi()
+ compile 'commons-io:commons-io:2.4'
+ compile 'commons-codec:commons-codec:1.10'
+ compile project(":compiler")
+}
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ pom.artifactId = 'dataBinder'
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/data-binding/gradlePlugin/gradle/wrapper/gradle-wrapper.jar b/tools/data-binding/gradlePlugin/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..3d0dee6
--- /dev/null
+++ b/tools/data-binding/gradlePlugin/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/tools/data-binding/gradlePlugin/gradle/wrapper/gradle-wrapper.properties b/tools/data-binding/gradlePlugin/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..8b80a06
--- /dev/null
+++ b/tools/data-binding/gradlePlugin/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Dec 11 16:01:54 PST 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-bin.zip
diff --git a/tools/data-binding/gradlePlugin/gradlew b/tools/data-binding/gradlePlugin/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/tools/data-binding/gradlePlugin/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/tools/data-binding/gradlePlugin/gradlew.bat b/tools/data-binding/gradlePlugin/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/tools/data-binding/gradlePlugin/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/tools/data-binding/gradlePlugin/src/main/kotlin/DataBindingProcessLayoutsTask.kt b/tools/data-binding/gradlePlugin/src/main/kotlin/DataBindingProcessLayoutsTask.kt
new file mode 100644
index 0000000..f3e1f1d
--- /dev/null
+++ b/tools/data-binding/gradlePlugin/src/main/kotlin/DataBindingProcessLayoutsTask.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.tool
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.TaskAction
+import kotlin.properties.Delegates
+import android.databinding.tool.util.Log
+import java.io.File
+
+open class DataBindingProcessLayoutsTask : DefaultTask() {
+ {
+ Log.d {"created data binding task"}
+ }
+ var xmlProcessor: LayoutXmlProcessor by Delegates.notNull()
+ var sdkDir : File by Delegates.notNull()
+ [TaskAction]
+ public fun doIt() {
+ Log.d {"running process layouts task"}
+ xmlProcessor.processResources()
+ }
+
+ public fun writeFiles(xmlOutFolder : File) {
+ xmlProcessor.writeIntermediateFile(sdkDir, xmlOutFolder)
+ }
+}
\ No newline at end of file
diff --git a/tools/data-binding/gradlePlugin/src/main/kotlin/plugin.kt b/tools/data-binding/gradlePlugin/src/main/kotlin/plugin.kt
new file mode 100644
index 0000000..5515adf
--- /dev/null
+++ b/tools/data-binding/gradlePlugin/src/main/kotlin/plugin.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import com.android.build.gradle.AppExtension
+import com.android.build.gradle.internal.api.ApplicationVariantImpl
+import com.android.build.gradle.internal.variant.ApplicationVariantData
+import java.io.File
+import org.gradle.api.file.FileCollection
+import android.databinding.tool.writer.JavaFileWriter
+import android.databinding.tool.util.Log
+import org.gradle.api.Action
+import com.android.build.gradle.BaseExtension
+import com.android.build.gradle.LibraryExtension
+import com.android.build.gradle.api.LibraryVariant
+import com.android.build.gradle.api.ApplicationVariant
+import com.android.build.gradle.internal.variant.BaseVariantData
+import com.android.build.gradle.internal.variant.LibraryVariantData
+import com.android.build.gradle.internal.api.LibraryVariantImpl
+import com.android.build.gradle.api.TestVariant
+import com.android.build.gradle.internal.variant.TestVariantData
+import com.android.build.gradle.internal.api.TestVariantImpl
+
+class DataBinderPlugin : Plugin<Project> {
+
+ inner class GradleFileWriter(var outputBase: String) : JavaFileWriter() {
+ override fun writeToFile(canonicalName: String, contents: String) {
+ val f = File("$outputBase/${canonicalName.replaceAll("\\.", "/")}.java")
+ log("Asked to write to ${canonicalName}. outputting to:${f.getAbsolutePath()}")
+ f.getParentFile().mkdirs()
+ f.writeText(contents, "utf-8")
+ }
+ }
+
+ override fun apply(project: Project?) {
+ if (project == null) return
+ project.afterEvaluate {
+ createXmlProcessor(project)
+ }
+ }
+
+ fun log(s: String) {
+ System.out.println("[qwqw data binding]: $s")
+ }
+
+ fun createXmlProcessor(p: Project) {
+ val androidExt = p.getExtensions().getByName("android")
+ if (androidExt !is BaseExtension) {
+ return
+ }
+ log("project build dir:${p.getBuildDir()}")
+ // TODO this will differ per flavor
+
+ if (androidExt is AppExtension) {
+ createXmlProcessorForApp(p, androidExt)
+ } else if (androidExt is LibraryExtension) {
+ createXmlProcessorForLibrary(p, androidExt)
+ } else {
+ throw RuntimeException("cannot understand android extension. What is it? ${androidExt}")
+ }
+ }
+
+ fun createXmlProcessorForLibrary(project : Project, lib : LibraryExtension) {
+ val sdkDir = lib.getSdkDirectory()
+ lib.getTestVariants().forEach { variant ->
+ log("test variant $variant. dir name ${variant.getDirName()}")
+ val variantData = getVariantData(variant)
+ attachXmlProcessor(project, variantData, sdkDir, false)//tests extend apk variant
+ }
+ lib.getLibraryVariants().forEach { variant ->
+ log("lib variant $variant . dir name ${variant.getDirName()}")
+ val variantData = getVariantData(variant)
+ attachXmlProcessor(project, variantData, sdkDir, true)
+ }
+ }
+
+ fun getVariantData(appVariant : LibraryVariant) : LibraryVariantData {
+ val clazz = javaClass<LibraryVariantImpl>()
+ val field = clazz.getDeclaredField("variantData")
+ field.setAccessible(true)
+ return field.get(appVariant) as LibraryVariantData
+ }
+
+ fun getVariantData(testVariant : TestVariant) : TestVariantData {
+ val clazz = javaClass<TestVariantImpl>()
+ val field = clazz.getDeclaredField("variantData")
+ field.setAccessible(true)
+ return field.get(testVariant) as TestVariantData
+ }
+
+ fun getVariantData(appVariant : ApplicationVariant) : ApplicationVariantData {
+ val clazz = javaClass<ApplicationVariantImpl>()
+ val field = clazz.getDeclaredField("variantData")
+ field.setAccessible(true)
+ return field.get(appVariant) as ApplicationVariantData
+ }
+
+ fun createXmlProcessorForApp(project : Project, appExt: AppExtension) {
+ val sdkDir = appExt.getSdkDirectory()
+ appExt.getTestVariants().forEach { testVariant ->
+ val variantData = getVariantData(testVariant)
+ attachXmlProcessor(project, variantData, sdkDir, false)
+ }
+ appExt.getApplicationVariants().forEach { appVariant ->
+ val variantData = getVariantData(appVariant)
+ attachXmlProcessor(project, variantData, sdkDir, false)
+ }
+ }
+
+ fun attachXmlProcessor(project : Project, variantData : BaseVariantData<*>, sdkDir : File,
+ isLibrary : Boolean) {
+ val configuration = variantData.getVariantConfiguration()
+ val minSdkVersion = configuration.getMinSdkVersion()
+ val generateRTask = variantData.generateRClassTask
+ val packageName = generateRTask.getPackageForR()
+ log("r task name $generateRTask . text symbols output dir: ${generateRTask.getTextSymbolOutputDir()}")
+ val fullName = configuration.getFullName()
+ val sources = variantData.getJavaSources()
+ sources.forEach({
+ if (it is FileCollection) {
+ it.forEach {
+ log("sources for ${variantData} ${it}}")
+ }
+ } else {
+ log("sources for ${variantData}: ${it}");
+ }
+ })
+ val resourceFolders = arrayListOf(variantData.mergeResourcesTask.getOutputDir())
+ log("MERGE RES OUTPUT ${variantData.mergeResourcesTask.getOutputDir()}")
+ val codeGenTargetFolder = generateRTask.getSourceOutputDir()
+ // TODO unnecessary?
+
+ // TODO attach to test module as well!
+
+ variantData.addJavaSourceFoldersToModel(codeGenTargetFolder)
+ val writerOutBase = codeGenTargetFolder.getAbsolutePath();
+ val fileWriter = GradleFileWriter(writerOutBase)
+ val xmlProcessor = LayoutXmlProcessor(packageName, resourceFolders, fileWriter,
+ minSdkVersion.getApiLevel(), isLibrary)
+ val processResTask = generateRTask
+
+ val xmlOutDir = "${project.getBuildDir()}/layout-info/${configuration.getDirName()}";
+ log("xml output for ${variantData} is ${xmlOutDir}")
+ val dataBindingTaskName = "dataBinding${processResTask.getName().capitalize()}"
+ log("created task $dataBindingTaskName")
+ project.getTasks().create(dataBindingTaskName,
+ javaClass<DataBindingProcessLayoutsTask>(),
+ object : Action<DataBindingProcessLayoutsTask> {
+ override fun execute(task: DataBindingProcessLayoutsTask) {
+ task.xmlProcessor = xmlProcessor
+ task.sdkDir = sdkDir
+ Log.d { "TASK adding dependency on ${task} for ${processResTask}" }
+ processResTask.dependsOn(task)
+ processResTask.getDependsOn().filterNot { it == task }.forEach {
+ Log.d { "adding dependency on ${it} for ${task}" }
+ task.dependsOn(it)
+ }
+ processResTask.doLast {
+ task.writeFiles(File(xmlOutDir))
+ }
+ }
+ });
+
+ if (isLibrary) {
+ val packageJarTaskName = "package${fullName.capitalize()}Jar"
+ val packageTask = project.getTasks().findByName(packageJarTaskName)
+ if (packageTask !is org.gradle.api.tasks.bundling.Jar) {
+ throw RuntimeException("cannot find package task in $project $variantData project $packageJarTaskName")
+ }
+ val excludePattern = "android/databinding/layouts/*.*"
+ val appPkgAsClass = packageName.replace('.', '/')
+ packageTask.exclude(excludePattern)
+ packageTask.exclude("$appPkgAsClass/databinding/*")
+ packageTask.exclude("$appPkgAsClass/BR.*")
+ packageTask.exclude(xmlProcessor.getInfoClassFullName().replace('.', '/') + ".class")
+ log("excludes ${packageTask.getExcludes()}")
+ }
+ }
+}
diff --git a/tools/data-binding/gradlePlugin/src/main/resources/META-INF/gradle-plugins/com.android.databinding.properties b/tools/data-binding/gradlePlugin/src/main/resources/META-INF/gradle-plugins/com.android.databinding.properties
new file mode 100644
index 0000000..2e04d00
--- /dev/null
+++ b/tools/data-binding/gradlePlugin/src/main/resources/META-INF/gradle-plugins/com.android.databinding.properties
@@ -0,0 +1,17 @@
+#
+# Copyright (C) 2014 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.
+#
+
+implementation-class=android.databinding.tool.DataBinderPlugin
\ No newline at end of file
diff --git a/tools/data-binding/gradlew b/tools/data-binding/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/tools/data-binding/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/tools/data-binding/grammarBuilder/BindingExpression.g4 b/tools/data-binding/grammarBuilder/BindingExpression.g4
new file mode 100644
index 0000000..3142507
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/BindingExpression.g4
@@ -0,0 +1,488 @@
+/*
+ [The "BSD licence"]
+ Copyright (c) 2013 Terence Parr, Sam Harwell
+ All rights reserved.
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+grammar BindingExpression;
+
+bindingSyntax
+ : expression defaults?
+ ;
+
+defaults
+ : ',' 'default' '=' constantValue
+ ;
+constantValue
+ : literal
+ | ResourceReference
+ | identifier
+ ;
+
+expression
+ : '(' expression ')' # Grouping
+// this isn't allowed yet.
+// | THIS # Primary
+ | literal # Primary
+ | identifier # Primary
+ | classExtraction # Primary
+ | resources # Resource
+// | typeArguments (explicitGenericInvocationSuffix | 'this' arguments) # GenericCall
+ | expression '.' Identifier # DotOp
+// | expression '.' 'this' # ThisReference
+// | expression '.' explicitGenericInvocation # ExplicitGenericInvocationOp
+ | expression '[' expression ']' # BracketOp
+ | target=expression '.' methodName=Identifier '(' args=expressionList? ')' # MethodInvocation
+ | '(' type ')' expression # CastOp
+ | op=('+'|'-') expression # UnaryOp
+ | op=('~'|'!') expression # UnaryOp
+ | left=expression op=('*'|'/'|'%') right=expression # MathOp
+ | left=expression op=('+'|'-') right=expression # MathOp
+ | left=expression op=('<<' | '>>>' | '>>') right=expression # BitShiftOp
+ | left=expression op=('<=' | '>=' | '>' | '<') right=expression # ComparisonOp
+ | expression 'instanceof' type # InstanceOfOp
+ | left=expression op=('==' | '!=') right=expression # ComparisonOp
+ | left=expression op='&' right=expression # BinaryOp
+ | left=expression op='^' right=expression # BinaryOp
+ | left=expression op='|' right=expression # BinaryOp
+ | left=expression op='&&' right=expression # AndOrOp
+ | left=expression op='||' right=expression # AndOrOp
+ | left=expression op='?' iftrue=expression ':' iffalse=expression # TernaryOp
+ | left=expression op='??' right=expression # QuestionQuestionOp
+ ;
+
+THIS
+ : 'this'
+ ;
+
+classExtraction
+ : type '.' 'class'
+ | 'void' '.' 'class'
+ ;
+
+expressionList
+ : expression (',' expression)*
+ ;
+
+literal
+ : javaLiteral
+ | stringLiteral
+ ;
+
+identifier
+ : Identifier
+ ;
+
+javaLiteral
+ : IntegerLiteral
+ | FloatingPointLiteral
+ | BooleanLiteral
+ | NullLiteral
+ | CharacterLiteral
+ ;
+
+stringLiteral
+ : SingleQuoteString
+ | DoubleQuoteString
+ ;
+
+explicitGenericInvocation
+ : typeArguments explicitGenericInvocationSuffix
+ ;
+
+typeArguments
+ : '<' type (',' type)* '>'
+ ;
+
+type
+ : classOrInterfaceType ('[' ']')*
+ | primitiveType ('[' ']')*
+ ;
+
+explicitGenericInvocationSuffix
+ : Identifier arguments
+ ;
+
+arguments
+ : '(' expressionList? ')'
+ ;
+
+classOrInterfaceType
+ : identifier typeArguments? ('.' Identifier typeArguments? )*
+ ;
+
+primitiveType
+ : 'boolean'
+ | 'char'
+ | 'byte'
+ | 'short'
+ | 'int'
+ | 'long'
+ | 'float'
+ | 'double'
+ ;
+
+resources
+ : ResourceReference resourceParameters?
+ ;
+
+resourceParameters
+ : '(' expressionList ')'
+ ;
+
+// LEXER
+
+// §3.10.1 Integer Literals
+
+IntegerLiteral
+ : DecimalIntegerLiteral
+ | HexIntegerLiteral
+ | OctalIntegerLiteral
+ | BinaryIntegerLiteral
+ ;
+
+fragment
+DecimalIntegerLiteral
+ : DecimalNumeral IntegerTypeSuffix?
+ ;
+
+fragment
+HexIntegerLiteral
+ : HexNumeral IntegerTypeSuffix?
+ ;
+
+fragment
+OctalIntegerLiteral
+ : OctalNumeral IntegerTypeSuffix?
+ ;
+
+fragment
+BinaryIntegerLiteral
+ : BinaryNumeral IntegerTypeSuffix?
+ ;
+
+fragment
+IntegerTypeSuffix
+ : [lL]
+ ;
+
+fragment
+DecimalNumeral
+ : '0'
+ | NonZeroDigit (Digits? | Underscores Digits)
+ ;
+
+fragment
+Digits
+ : Digit (DigitOrUnderscore* Digit)?
+ ;
+
+fragment
+Digit
+ : '0'
+ | NonZeroDigit
+ ;
+
+fragment
+NonZeroDigit
+ : [1-9]
+ ;
+
+fragment
+DigitOrUnderscore
+ : Digit
+ | '_'
+ ;
+
+fragment
+Underscores
+ : '_'+
+ ;
+
+fragment
+HexNumeral
+ : '0' [xX] HexDigits
+ ;
+
+fragment
+HexDigits
+ : HexDigit (HexDigitOrUnderscore* HexDigit)?
+ ;
+
+fragment
+HexDigit
+ : [0-9a-fA-F]
+ ;
+
+fragment
+HexDigitOrUnderscore
+ : HexDigit
+ | '_'
+ ;
+
+fragment
+OctalNumeral
+ : '0' Underscores? OctalDigits
+ ;
+
+fragment
+OctalDigits
+ : OctalDigit (OctalDigitOrUnderscore* OctalDigit)?
+ ;
+
+fragment
+OctalDigit
+ : [0-7]
+ ;
+
+fragment
+OctalDigitOrUnderscore
+ : OctalDigit
+ | '_'
+ ;
+
+fragment
+BinaryNumeral
+ : '0' [bB] BinaryDigits
+ ;
+
+fragment
+BinaryDigits
+ : BinaryDigit (BinaryDigitOrUnderscore* BinaryDigit)?
+ ;
+
+fragment
+BinaryDigit
+ : [01]
+ ;
+
+fragment
+BinaryDigitOrUnderscore
+ : BinaryDigit
+ | '_'
+ ;
+
+// §3.10.2 Floating-Point Literals
+
+FloatingPointLiteral
+ : DecimalFloatingPointLiteral
+ | HexadecimalFloatingPointLiteral
+ ;
+
+fragment
+DecimalFloatingPointLiteral
+ : Digits '.' Digits? ExponentPart? FloatTypeSuffix?
+ | '.' Digits ExponentPart? FloatTypeSuffix?
+ | Digits ExponentPart FloatTypeSuffix?
+ | Digits FloatTypeSuffix
+ ;
+
+fragment
+ExponentPart
+ : ExponentIndicator SignedInteger
+ ;
+
+fragment
+ExponentIndicator
+ : [eE]
+ ;
+
+fragment
+SignedInteger
+ : Sign? Digits
+ ;
+
+fragment
+Sign
+ : [+-]
+ ;
+
+fragment
+FloatTypeSuffix
+ : [fFdD]
+ ;
+
+fragment
+HexadecimalFloatingPointLiteral
+ : HexSignificand BinaryExponent FloatTypeSuffix?
+ ;
+
+fragment
+HexSignificand
+ : HexNumeral '.'?
+ | '0' [xX] HexDigits? '.' HexDigits
+ ;
+
+fragment
+BinaryExponent
+ : BinaryExponentIndicator SignedInteger
+ ;
+
+fragment
+BinaryExponentIndicator
+ : [pP]
+ ;
+
+// §3.10.3 Boolean Literals
+
+BooleanLiteral
+ : 'true'
+ | 'false'
+ ;
+
+// §3.10.4 Character Literals
+
+CharacterLiteral
+ : '\'' SingleCharacter '\''
+ | '\'' EscapeSequence '\''
+ ;
+
+fragment
+SingleCharacter
+ : ~['\\]
+ ;
+// §3.10.5 String Literals
+SingleQuoteString
+ : '`' SingleQuoteStringCharacter* '`'
+ ;
+
+DoubleQuoteString
+ : '"' StringCharacters? '"'
+ ;
+
+fragment
+StringCharacters
+ : StringCharacter+
+ ;
+fragment
+StringCharacter
+ : ~["\\]
+ | EscapeSequence
+ ;
+fragment
+SingleQuoteStringCharacter
+ : ~[`\\]
+ | EscapeSequence
+ ;
+
+// §3.10.6 Escape Sequences for Character and String Literals
+fragment
+EscapeSequence
+ : '\\' [btnfr"'`\\]
+ | OctalEscape
+ | UnicodeEscape
+ ;
+
+fragment
+OctalEscape
+ : '\\' OctalDigit
+ | '\\' OctalDigit OctalDigit
+ | '\\' ZeroToThree OctalDigit OctalDigit
+ ;
+
+fragment
+UnicodeEscape
+ : '\\' 'u' HexDigit HexDigit HexDigit HexDigit
+ ;
+
+fragment
+ZeroToThree
+ : [0-3]
+ ;
+
+// §3.10.7 The Null Literal
+
+NullLiteral
+ : 'null'
+ ;
+
+// §3.8 Identifiers (must appear after all keywords in the grammar)
+
+Identifier
+ : JavaLetter JavaLetterOrDigit*
+ ;
+
+fragment
+JavaLetter
+ : [a-zA-Z$_] // these are the "java letters" below 0xFF
+ | // covers all characters above 0xFF which are not a surrogate
+ ~[\u0000-\u00FF\uD800-\uDBFF]
+ {Character.isJavaIdentifierStart(_input.LA(-1))}?
+ | // covers UTF-16 surrogate pairs encodings for U+10000 to U+10FFFF
+ [\uD800-\uDBFF] [\uDC00-\uDFFF]
+ {Character.isJavaIdentifierStart(Character.toCodePoint((char)_input.LA(-2), (char)_input.LA(-1)))}?
+ ;
+
+fragment
+JavaLetterOrDigit
+ : [a-zA-Z0-9$_] // these are the "java letters or digits" below 0xFF
+ | // covers all characters above 0xFF which are not a surrogate
+ ~[\u0000-\u00FF\uD800-\uDBFF]
+ {Character.isJavaIdentifierPart(_input.LA(-1))}?
+ | // covers UTF-16 surrogate pairs encodings for U+10000 to U+10FFFF
+ [\uD800-\uDBFF] [\uDC00-\uDFFF]
+ {Character.isJavaIdentifierPart(Character.toCodePoint((char)_input.LA(-2), (char)_input.LA(-1)))}?
+ ;
+
+//
+// Whitespace and comments
+//
+
+WS : [ \t\r\n\u000C]+ -> skip
+ ;
+
+//
+// Resource references
+//
+
+ResourceReference
+ : '@' (PackageName ':')? ResourceType '/' Identifier
+ ;
+
+PackageName
+ : 'android'
+ | Identifier
+ ;
+
+ResourceType
+ : 'anim'
+ | 'animator'
+ | 'bool'
+ | 'color'
+ | 'colorStateList'
+ | 'dimen'
+ | 'dimenOffset'
+ | 'dimenSize'
+ | 'drawable'
+ | 'fraction'
+ | 'id'
+ | 'integer'
+ | 'intArray'
+ | 'interpolator'
+ | 'layout'
+ | 'plurals'
+ | 'stateListAnimator'
+ | 'string'
+ | 'stringArray'
+ | 'transition'
+ | 'typedArray'
+ ;
diff --git a/tools/data-binding/grammarBuilder/build.gradle b/tools/data-binding/grammarBuilder/build.gradle
new file mode 100644
index 0000000..b5f85a1
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/build.gradle
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+apply plugin: 'java'
+apply plugin: 'application'
+apply plugin: 'maven'
+
+sourceCompatibility = config.javaTargetCompatibility
+targetCompatibility = config.javaSourceCompatibility
+
+mainClassName = "org.antlr.v4.Tool"
+
+run {
+ args "BindingExpression.g4", "-visitor", "-o", "src/main/java-gen/android/databinding/parser", "-package", "android.databinding.parser"
+}
+
+sourceSets {
+ main {
+ java {
+ srcDir 'src/main/java'
+ srcDir 'src/main/java-gen'
+ }
+ }
+ test {
+ java {
+ srcDir 'src/test/java'
+ }
+ }
+}
+
+dependencies {
+ compile 'com.tunnelvisionlabs:antlr4:4.4'
+ testCompile 'junit:junit:4.11'
+}
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ pom.artifactId = 'grammarBuilder'
+ }
+ }
+}
diff --git a/tools/data-binding/grammarBuilder/gradle/wrapper/gradle-wrapper.jar b/tools/data-binding/grammarBuilder/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..3d0dee6
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/tools/data-binding/grammarBuilder/gradle/wrapper/gradle-wrapper.properties b/tools/data-binding/grammarBuilder/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..b8e77ce
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Dec 11 16:05:17 PST 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-bin.zip
diff --git a/tools/data-binding/grammarBuilder/gradlew b/tools/data-binding/grammarBuilder/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/tools/data-binding/grammarBuilder/gradlew.bat b/tools/data-binding/grammarBuilder/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpression.tokens b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpression.tokens
new file mode 100644
index 0000000..d379280
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpression.tokens
@@ -0,0 +1,101 @@
+NullLiteral=51
+T__29=14
+T__28=15
+T__27=16
+T__26=17
+T__25=18
+T__24=19
+T__23=20
+T__22=21
+CharacterLiteral=48
+T__21=22
+T__20=23
+SingleQuoteString=49
+T__9=34
+T__8=35
+Identifier=52
+T__7=36
+T__6=37
+T__5=38
+T__4=39
+T__19=24
+T__16=27
+T__15=28
+T__18=25
+T__17=26
+T__12=31
+T__11=32
+T__14=29
+T__13=30
+T__10=33
+THIS=44
+PackageName=55
+DoubleQuoteString=50
+T__42=1
+T__40=3
+T__41=2
+ResourceType=56
+T__30=13
+T__31=12
+T__32=11
+WS=53
+T__33=10
+T__34=9
+T__35=8
+T__36=7
+T__37=6
+T__38=5
+T__39=4
+T__1=42
+T__0=43
+FloatingPointLiteral=46
+T__3=40
+T__2=41
+IntegerLiteral=45
+ResourceReference=54
+BooleanLiteral=47
+'!'=43
+'instanceof'=42
+'|'=41
+'class'=40
+'>='=39
+'~'=38
+'/'=37
+'=='=36
+'??'=35
+'null'=51
+'>'=34
+'||'=33
+'this'=44
+'&&'=32
+'='=31
+'+'=30
+'.'=29
+')'=28
+'byte'=27
+'^'=26
+'%'=25
+'>>'=23
+'char'=24
+'float'=22
+'boolean'=21
+'double'=20
+'<<'=18
+'void'=19
+'?'=17
+'<='=16
+'!='=15
+'<'=13
+'int'=14
+':'=12
+'('=11
+'-'=10
+'['=9
+'*'=8
+','=7
+'default'=6
+'&'=5
+'short'=4
+']'=3
+'>>>'=2
+'long'=1
diff --git a/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionBaseListener.java b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionBaseListener.java
new file mode 100644
index 0000000..db87538
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionBaseListener.java
@@ -0,0 +1,495 @@
+// Generated from BindingExpression.g4 by ANTLR 4.4
+package android.databinding.parser;
+
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.misc.NotNull;
+import org.antlr.v4.runtime.tree.ErrorNode;
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+/**
+ * This class provides an empty implementation of {@link BindingExpressionListener},
+ * which can be extended to create a listener which only needs to handle a subset
+ * of the available methods.
+ */
+public class BindingExpressionBaseListener implements BindingExpressionListener {
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterExpression(@NotNull BindingExpressionParser.ExpressionContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitExpression(@NotNull BindingExpressionParser.ExpressionContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterResources(@NotNull BindingExpressionParser.ResourcesContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitResources(@NotNull BindingExpressionParser.ResourcesContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterBracketOp(@NotNull BindingExpressionParser.BracketOpContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitBracketOp(@NotNull BindingExpressionParser.BracketOpContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterUnaryOp(@NotNull BindingExpressionParser.UnaryOpContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitUnaryOp(@NotNull BindingExpressionParser.UnaryOpContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterCastOp(@NotNull BindingExpressionParser.CastOpContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitCastOp(@NotNull BindingExpressionParser.CastOpContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterResourceParameters(@NotNull BindingExpressionParser.ResourceParametersContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitResourceParameters(@NotNull BindingExpressionParser.ResourceParametersContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterAndOrOp(@NotNull BindingExpressionParser.AndOrOpContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitAndOrOp(@NotNull BindingExpressionParser.AndOrOpContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterMethodInvocation(@NotNull BindingExpressionParser.MethodInvocationContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitMethodInvocation(@NotNull BindingExpressionParser.MethodInvocationContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterExpressionList(@NotNull BindingExpressionParser.ExpressionListContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitExpressionList(@NotNull BindingExpressionParser.ExpressionListContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterClassOrInterfaceType(@NotNull BindingExpressionParser.ClassOrInterfaceTypeContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitClassOrInterfaceType(@NotNull BindingExpressionParser.ClassOrInterfaceTypeContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterStringLiteral(@NotNull BindingExpressionParser.StringLiteralContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitStringLiteral(@NotNull BindingExpressionParser.StringLiteralContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterPrimary(@NotNull BindingExpressionParser.PrimaryContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitPrimary(@NotNull BindingExpressionParser.PrimaryContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterType(@NotNull BindingExpressionParser.TypeContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitType(@NotNull BindingExpressionParser.TypeContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterBindingSyntax(@NotNull BindingExpressionParser.BindingSyntaxContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitBindingSyntax(@NotNull BindingExpressionParser.BindingSyntaxContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterComparisonOp(@NotNull BindingExpressionParser.ComparisonOpContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitComparisonOp(@NotNull BindingExpressionParser.ComparisonOpContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterTernaryOp(@NotNull BindingExpressionParser.TernaryOpContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitTernaryOp(@NotNull BindingExpressionParser.TernaryOpContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterConstantValue(@NotNull BindingExpressionParser.ConstantValueContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitConstantValue(@NotNull BindingExpressionParser.ConstantValueContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterDotOp(@NotNull BindingExpressionParser.DotOpContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitDotOp(@NotNull BindingExpressionParser.DotOpContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterDefaults(@NotNull BindingExpressionParser.DefaultsContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitDefaults(@NotNull BindingExpressionParser.DefaultsContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterBitShiftOp(@NotNull BindingExpressionParser.BitShiftOpContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitBitShiftOp(@NotNull BindingExpressionParser.BitShiftOpContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterInstanceOfOp(@NotNull BindingExpressionParser.InstanceOfOpContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitInstanceOfOp(@NotNull BindingExpressionParser.InstanceOfOpContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterBinaryOp(@NotNull BindingExpressionParser.BinaryOpContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitBinaryOp(@NotNull BindingExpressionParser.BinaryOpContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterExplicitGenericInvocation(@NotNull BindingExpressionParser.ExplicitGenericInvocationContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitExplicitGenericInvocation(@NotNull BindingExpressionParser.ExplicitGenericInvocationContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterResource(@NotNull BindingExpressionParser.ResourceContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitResource(@NotNull BindingExpressionParser.ResourceContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterTypeArguments(@NotNull BindingExpressionParser.TypeArgumentsContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitTypeArguments(@NotNull BindingExpressionParser.TypeArgumentsContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterGrouping(@NotNull BindingExpressionParser.GroupingContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitGrouping(@NotNull BindingExpressionParser.GroupingContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterMathOp(@NotNull BindingExpressionParser.MathOpContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitMathOp(@NotNull BindingExpressionParser.MathOpContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterClassExtraction(@NotNull BindingExpressionParser.ClassExtractionContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitClassExtraction(@NotNull BindingExpressionParser.ClassExtractionContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterArguments(@NotNull BindingExpressionParser.ArgumentsContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitArguments(@NotNull BindingExpressionParser.ArgumentsContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterPrimitiveType(@NotNull BindingExpressionParser.PrimitiveTypeContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitPrimitiveType(@NotNull BindingExpressionParser.PrimitiveTypeContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterQuestionQuestionOp(@NotNull BindingExpressionParser.QuestionQuestionOpContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitQuestionQuestionOp(@NotNull BindingExpressionParser.QuestionQuestionOpContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterJavaLiteral(@NotNull BindingExpressionParser.JavaLiteralContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitJavaLiteral(@NotNull BindingExpressionParser.JavaLiteralContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterExplicitGenericInvocationSuffix(@NotNull BindingExpressionParser.ExplicitGenericInvocationSuffixContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitExplicitGenericInvocationSuffix(@NotNull BindingExpressionParser.ExplicitGenericInvocationSuffixContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterIdentifier(@NotNull BindingExpressionParser.IdentifierContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitIdentifier(@NotNull BindingExpressionParser.IdentifierContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterLiteral(@NotNull BindingExpressionParser.LiteralContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitLiteral(@NotNull BindingExpressionParser.LiteralContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterEveryRule(@NotNull ParserRuleContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitEveryRule(@NotNull ParserRuleContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void visitTerminal(@NotNull TerminalNode node) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void visitErrorNode(@NotNull ErrorNode node) { }
+}
\ No newline at end of file
diff --git a/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionBaseVisitor.java b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionBaseVisitor.java
new file mode 100644
index 0000000..d7d426e
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionBaseVisitor.java
@@ -0,0 +1,295 @@
+// Generated from BindingExpression.g4 by ANTLR 4.4
+package android.databinding.parser;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.misc.NotNull;
+import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor;
+
+/**
+ * This class provides an empty implementation of {@link BindingExpressionVisitor},
+ * which can be extended to create a visitor which only needs to handle a subset
+ * of the available methods.
+ *
+ * @param <Result> The return type of the visit operation. Use {@link Void} for
+ * operations with no return type.
+ */
+public class BindingExpressionBaseVisitor<Result> extends AbstractParseTreeVisitor<Result> implements BindingExpressionVisitor<Result> {
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitExpression(@NotNull BindingExpressionParser.ExpressionContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitResources(@NotNull BindingExpressionParser.ResourcesContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitBracketOp(@NotNull BindingExpressionParser.BracketOpContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitUnaryOp(@NotNull BindingExpressionParser.UnaryOpContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitCastOp(@NotNull BindingExpressionParser.CastOpContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitResourceParameters(@NotNull BindingExpressionParser.ResourceParametersContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitAndOrOp(@NotNull BindingExpressionParser.AndOrOpContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitMethodInvocation(@NotNull BindingExpressionParser.MethodInvocationContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitExpressionList(@NotNull BindingExpressionParser.ExpressionListContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitClassOrInterfaceType(@NotNull BindingExpressionParser.ClassOrInterfaceTypeContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitStringLiteral(@NotNull BindingExpressionParser.StringLiteralContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitPrimary(@NotNull BindingExpressionParser.PrimaryContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitType(@NotNull BindingExpressionParser.TypeContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitBindingSyntax(@NotNull BindingExpressionParser.BindingSyntaxContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitComparisonOp(@NotNull BindingExpressionParser.ComparisonOpContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitTernaryOp(@NotNull BindingExpressionParser.TernaryOpContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitConstantValue(@NotNull BindingExpressionParser.ConstantValueContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitDotOp(@NotNull BindingExpressionParser.DotOpContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitDefaults(@NotNull BindingExpressionParser.DefaultsContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitBitShiftOp(@NotNull BindingExpressionParser.BitShiftOpContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitInstanceOfOp(@NotNull BindingExpressionParser.InstanceOfOpContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitBinaryOp(@NotNull BindingExpressionParser.BinaryOpContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitExplicitGenericInvocation(@NotNull BindingExpressionParser.ExplicitGenericInvocationContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitResource(@NotNull BindingExpressionParser.ResourceContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitTypeArguments(@NotNull BindingExpressionParser.TypeArgumentsContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitGrouping(@NotNull BindingExpressionParser.GroupingContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitMathOp(@NotNull BindingExpressionParser.MathOpContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitClassExtraction(@NotNull BindingExpressionParser.ClassExtractionContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitArguments(@NotNull BindingExpressionParser.ArgumentsContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitPrimitiveType(@NotNull BindingExpressionParser.PrimitiveTypeContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitQuestionQuestionOp(@NotNull BindingExpressionParser.QuestionQuestionOpContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitJavaLiteral(@NotNull BindingExpressionParser.JavaLiteralContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitExplicitGenericInvocationSuffix(@NotNull BindingExpressionParser.ExplicitGenericInvocationSuffixContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitIdentifier(@NotNull BindingExpressionParser.IdentifierContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitLiteral(@NotNull BindingExpressionParser.LiteralContext ctx) { return visitChildren(ctx); }
+}
\ No newline at end of file
diff --git a/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionLexer.java b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionLexer.java
new file mode 100644
index 0000000..47eb769
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionLexer.java
@@ -0,0 +1,413 @@
+// Generated from BindingExpression.g4 by ANTLR 4.4
+package android.databinding.parser;
+import org.antlr.v4.runtime.Lexer;
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.TokenStream;
+import org.antlr.v4.runtime.*;
+import org.antlr.v4.runtime.atn.*;
+import org.antlr.v4.runtime.dfa.DFA;
+import org.antlr.v4.runtime.misc.*;
+
+public class BindingExpressionLexer extends Lexer {
+ public static final int
+ T__42=1, T__41=2, T__40=3, T__39=4, T__38=5, T__37=6, T__36=7, T__35=8,
+ T__34=9, T__33=10, T__32=11, T__31=12, T__30=13, T__29=14, T__28=15, T__27=16,
+ T__26=17, T__25=18, T__24=19, T__23=20, T__22=21, T__21=22, T__20=23,
+ T__19=24, T__18=25, T__17=26, T__16=27, T__15=28, T__14=29, T__13=30,
+ T__12=31, T__11=32, T__10=33, T__9=34, T__8=35, T__7=36, T__6=37, T__5=38,
+ T__4=39, T__3=40, T__2=41, T__1=42, T__0=43, THIS=44, IntegerLiteral=45,
+ FloatingPointLiteral=46, BooleanLiteral=47, CharacterLiteral=48, SingleQuoteString=49,
+ DoubleQuoteString=50, NullLiteral=51, Identifier=52, WS=53, ResourceReference=54,
+ PackageName=55, ResourceType=56;
+ public static String[] modeNames = {
+ "DEFAULT_MODE"
+ };
+
+ public static final String[] tokenNames = {
+ "'\\u0000'", "'\\u0001'", "'\\u0002'", "'\\u0003'", "'\\u0004'", "'\\u0005'",
+ "'\\u0006'", "'\\u0007'", "'\b'", "'\t'", "'\n'", "'\\u000B'", "'\f'",
+ "'\r'", "'\\u000E'", "'\\u000F'", "'\\u0010'", "'\\u0011'", "'\\u0012'",
+ "'\\u0013'", "'\\u0014'", "'\\u0015'", "'\\u0016'", "'\\u0017'", "'\\u0018'",
+ "'\\u0019'", "'\\u001A'", "'\\u001B'", "'\\u001C'", "'\\u001D'", "'\\u001E'",
+ "'\\u001F'", "' '", "'!'", "'\"'", "'#'", "'$'", "'%'", "'&'", "'''",
+ "'('", "')'", "'*'", "'+'", "','", "'-'", "'.'", "'/'", "'0'", "'1'",
+ "'2'", "'3'", "'4'", "'5'", "'6'", "'7'", "'8'"
+ };
+ public static final String[] ruleNames = {
+ "T__42", "T__41", "T__40", "T__39", "T__38", "T__37", "T__36", "T__35",
+ "T__34", "T__33", "T__32", "T__31", "T__30", "T__29", "T__28", "T__27",
+ "T__26", "T__25", "T__24", "T__23", "T__22", "T__21", "T__20", "T__19",
+ "T__18", "T__17", "T__16", "T__15", "T__14", "T__13", "T__12", "T__11",
+ "T__10", "T__9", "T__8", "T__7", "T__6", "T__5", "T__4", "T__3", "T__2",
+ "T__1", "T__0", "THIS", "IntegerLiteral", "DecimalIntegerLiteral", "HexIntegerLiteral",
+ "OctalIntegerLiteral", "BinaryIntegerLiteral", "IntegerTypeSuffix", "DecimalNumeral",
+ "Digits", "Digit", "NonZeroDigit", "DigitOrUnderscore", "Underscores",
+ "HexNumeral", "HexDigits", "HexDigit", "HexDigitOrUnderscore", "OctalNumeral",
+ "OctalDigits", "OctalDigit", "OctalDigitOrUnderscore", "BinaryNumeral",
+ "BinaryDigits", "BinaryDigit", "BinaryDigitOrUnderscore", "FloatingPointLiteral",
+ "DecimalFloatingPointLiteral", "ExponentPart", "ExponentIndicator", "SignedInteger",
+ "Sign", "FloatTypeSuffix", "HexadecimalFloatingPointLiteral", "HexSignificand",
+ "BinaryExponent", "BinaryExponentIndicator", "BooleanLiteral", "CharacterLiteral",
+ "SingleCharacter", "SingleQuoteString", "DoubleQuoteString", "StringCharacters",
+ "StringCharacter", "SingleQuoteStringCharacter", "EscapeSequence", "OctalEscape",
+ "UnicodeEscape", "ZeroToThree", "NullLiteral", "Identifier", "JavaLetter",
+ "JavaLetterOrDigit", "WS", "ResourceReference", "PackageName", "ResourceType"
+ };
+
+
+ public BindingExpressionLexer(CharStream input) {
+ super(input);
+ _interp = new LexerATNSimulator(this,_ATN);
+ }
+
+ @Override
+ public String getGrammarFileName() { return "BindingExpression.g4"; }
+
+ @Override
+ public String[] getTokenNames() { return tokenNames; }
+
+ @Override
+ public String[] getRuleNames() { return ruleNames; }
+
+ @Override
+ public String getSerializedATN() { return _serializedATN; }
+
+ @Override
+ public String[] getModeNames() { return modeNames; }
+
+ @Override
+ public boolean sempred(RuleContext _localctx, int ruleIndex, int predIndex) {
+ switch (ruleIndex) {
+ case 93 : return JavaLetter_sempred(_localctx, predIndex);
+
+ case 94 : return JavaLetterOrDigit_sempred(_localctx, predIndex);
+ }
+ return true;
+ }
+ private boolean JavaLetterOrDigit_sempred(RuleContext _localctx, int predIndex) {
+ switch (predIndex) {
+ case 2: return Character.isJavaIdentifierPart(_input.LA(-1));
+
+ case 3: return Character.isJavaIdentifierPart(Character.toCodePoint((char)_input.LA(-2), (char)_input.LA(-1)));
+ }
+ return true;
+ }
+ private boolean JavaLetter_sempred(RuleContext _localctx, int predIndex) {
+ switch (predIndex) {
+ case 0: return Character.isJavaIdentifierStart(_input.LA(-1));
+
+ case 1: return Character.isJavaIdentifierStart(Character.toCodePoint((char)_input.LA(-2), (char)_input.LA(-1)));
+ }
+ return true;
+ }
+
+ public static final String _serializedATN =
+ "\3\uaf6f\u8320\u479d\ub75c\u4880\u1605\u191c\uab37\2:\u0358\b\1\4\2\t"+
+ "\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13"+
+ "\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22"+
+ "\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30\4\31\t\31"+
+ "\4\32\t\32\4\33\t\33\4\34\t\34\4\35\t\35\4\36\t\36\4\37\t\37\4 \t \4!"+
+ "\t!\4\"\t\"\4#\t#\4$\t$\4%\t%\4&\t&\4\'\t\'\4(\t(\4)\t)\4*\t*\4+\t+\4"+
+ ",\t,\4-\t-\4.\t.\4/\t/\4\60\t\60\4\61\t\61\4\62\t\62\4\63\t\63\4\64\t"+
+ "\64\4\65\t\65\4\66\t\66\4\67\t\67\48\t8\49\t9\4:\t:\4;\t;\4<\t<\4=\t="+
+ "\4>\t>\4?\t?\4@\t@\4A\tA\4B\tB\4C\tC\4D\tD\4E\tE\4F\tF\4G\tG\4H\tH\4I"+
+ "\tI\4J\tJ\4K\tK\4L\tL\4M\tM\4N\tN\4O\tO\4P\tP\4Q\tQ\4R\tR\4S\tS\4T\tT"+
+ "\4U\tU\4V\tV\4W\tW\4X\tX\4Y\tY\4Z\tZ\4[\t[\4\\\t\\\4]\t]\4^\t^\4_\t_\4"+
+ "`\t`\4a\ta\4b\tb\4c\tc\4d\td\3\2\3\2\3\2\3\2\3\2\3\3\3\3\3\3\3\3\3\4\3"+
+ "\4\3\5\3\5\3\5\3\5\3\5\3\5\3\6\3\6\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\b"+
+ "\3\b\3\t\3\t\3\n\3\n\3\13\3\13\3\f\3\f\3\r\3\r\3\16\3\16\3\17\3\17\3\17"+
+ "\3\17\3\20\3\20\3\20\3\21\3\21\3\21\3\22\3\22\3\23\3\23\3\23\3\24\3\24"+
+ "\3\24\3\24\3\24\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\26\3\26\3\26\3\26"+
+ "\3\26\3\26\3\26\3\26\3\27\3\27\3\27\3\27\3\27\3\27\3\30\3\30\3\30\3\31"+
+ "\3\31\3\31\3\31\3\31\3\32\3\32\3\33\3\33\3\34\3\34\3\34\3\34\3\34\3\35"+
+ "\3\35\3\36\3\36\3\37\3\37\3 \3 \3!\3!\3!\3\"\3\"\3\"\3#\3#\3$\3$\3$\3"+
+ "%\3%\3%\3&\3&\3\'\3\'\3(\3(\3(\3)\3)\3)\3)\3)\3)\3*\3*\3+\3+\3+\3+\3+"+
+ "\3+\3+\3+\3+\3+\3+\3,\3,\3-\3-\3-\3-\3-\3.\3.\3.\3.\5.\u0168\n.\3/\3/"+
+ "\5/\u016c\n/\3\60\3\60\5\60\u0170\n\60\3\61\3\61\5\61\u0174\n\61\3\62"+
+ "\3\62\5\62\u0178\n\62\3\63\3\63\3\64\3\64\3\64\5\64\u017f\n\64\3\64\3"+
+ "\64\3\64\5\64\u0184\n\64\5\64\u0186\n\64\3\65\3\65\7\65\u018a\n\65\f\65"+
+ "\16\65\u018d\13\65\3\65\5\65\u0190\n\65\3\66\3\66\5\66\u0194\n\66\3\67"+
+ "\3\67\38\38\58\u019a\n8\39\69\u019d\n9\r9\169\u019e\3:\3:\3:\3:\3;\3;"+
+ "\7;\u01a7\n;\f;\16;\u01aa\13;\3;\5;\u01ad\n;\3<\3<\3=\3=\5=\u01b3\n=\3"+
+ ">\3>\5>\u01b7\n>\3>\3>\3?\3?\7?\u01bd\n?\f?\16?\u01c0\13?\3?\5?\u01c3"+
+ "\n?\3@\3@\3A\3A\5A\u01c9\nA\3B\3B\3B\3B\3C\3C\7C\u01d1\nC\fC\16C\u01d4"+
+ "\13C\3C\5C\u01d7\nC\3D\3D\3E\3E\5E\u01dd\nE\3F\3F\5F\u01e1\nF\3G\3G\3"+
+ "G\5G\u01e6\nG\3G\5G\u01e9\nG\3G\5G\u01ec\nG\3G\3G\3G\5G\u01f1\nG\3G\5"+
+ "G\u01f4\nG\3G\3G\3G\5G\u01f9\nG\3G\3G\3G\5G\u01fe\nG\3H\3H\3H\3I\3I\3"+
+ "J\5J\u0206\nJ\3J\3J\3K\3K\3L\3L\3M\3M\3M\5M\u0211\nM\3N\3N\5N\u0215\n"+
+ "N\3N\3N\3N\5N\u021a\nN\3N\3N\5N\u021e\nN\3O\3O\3O\3P\3P\3Q\3Q\3Q\3Q\3"+
+ "Q\3Q\3Q\3Q\3Q\5Q\u022e\nQ\3R\3R\3R\3R\3R\3R\3R\3R\5R\u0238\nR\3S\3S\3"+
+ "T\3T\7T\u023e\nT\fT\16T\u0241\13T\3T\3T\3U\3U\5U\u0247\nU\3U\3U\3V\6V"+
+ "\u024c\nV\rV\16V\u024d\3W\3W\5W\u0252\nW\3X\3X\5X\u0256\nX\3Y\3Y\3Y\3"+
+ "Y\5Y\u025c\nY\3Z\3Z\3Z\3Z\3Z\3Z\3Z\3Z\3Z\3Z\3Z\5Z\u0269\nZ\3[\3[\3[\3"+
+ "[\3[\3[\3[\3\\\3\\\3]\3]\3]\3]\3]\3^\3^\7^\u027b\n^\f^\16^\u027e\13^\3"+
+ "_\3_\3_\3_\3_\3_\5_\u0286\n_\3`\3`\3`\3`\3`\3`\5`\u028e\n`\3a\6a\u0291"+
+ "\na\ra\16a\u0292\3a\3a\3b\3b\3b\3b\5b\u029b\nb\3b\3b\3b\3b\3c\3c\3c\3"+
+ "c\3c\3c\3c\3c\5c\u02a9\nc\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3"+
+ "d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3"+
+ "d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3"+
+ "d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3"+
+ "d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3"+
+ "d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3"+
+ "d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3"+
+ "d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\3d\5d\u0357\n"+
+ "d\2\2\2e\3\2\3\5\2\4\7\2\5\t\2\6\13\2\7\r\2\b\17\2\t\21\2\n\23\2\13\25"+
+ "\2\f\27\2\r\31\2\16\33\2\17\35\2\20\37\2\21!\2\22#\2\23%\2\24\'\2\25)"+
+ "\2\26+\2\27-\2\30/\2\31\61\2\32\63\2\33\65\2\34\67\2\359\2\36;\2\37=\2"+
+ " ?\2!A\2\"C\2#E\2$G\2%I\2&K\2\'M\2(O\2)Q\2*S\2+U\2,W\2-Y\2.[\2/]\2\2_"+
+ "\2\2a\2\2c\2\2e\2\2g\2\2i\2\2k\2\2m\2\2o\2\2q\2\2s\2\2u\2\2w\2\2y\2\2"+
+ "{\2\2}\2\2\177\2\2\u0081\2\2\u0083\2\2\u0085\2\2\u0087\2\2\u0089\2\2\u008b"+
+ "\2\60\u008d\2\2\u008f\2\2\u0091\2\2\u0093\2\2\u0095\2\2\u0097\2\2\u0099"+
+ "\2\2\u009b\2\2\u009d\2\2\u009f\2\2\u00a1\2\61\u00a3\2\62\u00a5\2\2\u00a7"+
+ "\2\63\u00a9\2\64\u00ab\2\2\u00ad\2\2\u00af\2\2\u00b1\2\2\u00b3\2\2\u00b5"+
+ "\2\2\u00b7\2\2\u00b9\2\65\u00bb\2\66\u00bd\2\2\u00bf\2\2\u00c1\2\67\u00c3"+
+ "\28\u00c5\29\u00c7\2:\3\2\30\4\2NNnn\3\2\63;\4\2ZZzz\5\2\62;CHch\3\2\62"+
+ "9\4\2DDdd\3\2\62\63\4\2GGgg\4\2--//\6\2FFHHffhh\4\2RRrr\4\2))^^\4\2$$"+
+ "^^\4\2^^bb\13\2$$))^^bbddhhppttvv\3\2\62\65\6\2&&C\\aac|\4\2\2\u0101\ud802"+
+ "\udc01\3\2\ud802\udc01\3\2\udc02\ue001\7\2&&\62;C\\aac|\5\2\13\f\16\17"+
+ "\"\"\u037b\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2\2"+
+ "\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2\27"+
+ "\3\2\2\2\2\31\3\2\2\2\2\33\3\2\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2\2"+
+ "\2\2#\3\2\2\2\2%\3\2\2\2\2\'\3\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2\2"+
+ "\2/\3\2\2\2\2\61\3\2\2\2\2\63\3\2\2\2\2\65\3\2\2\2\2\67\3\2\2\2\29\3\2"+
+ "\2\2\2;\3\2\2\2\2=\3\2\2\2\2?\3\2\2\2\2A\3\2\2\2\2C\3\2\2\2\2E\3\2\2\2"+
+ "\2G\3\2\2\2\2I\3\2\2\2\2K\3\2\2\2\2M\3\2\2\2\2O\3\2\2\2\2Q\3\2\2\2\2S"+
+ "\3\2\2\2\2U\3\2\2\2\2W\3\2\2\2\2Y\3\2\2\2\2[\3\2\2\2\2\u008b\3\2\2\2\2"+
+ "\u00a1\3\2\2\2\2\u00a3\3\2\2\2\2\u00a7\3\2\2\2\2\u00a9\3\2\2\2\2\u00b9"+
+ "\3\2\2\2\2\u00bb\3\2\2\2\2\u00c1\3\2\2\2\2\u00c3\3\2\2\2\2\u00c5\3\2\2"+
+ "\2\2\u00c7\3\2\2\2\3\u00c9\3\2\2\2\5\u00ce\3\2\2\2\7\u00d2\3\2\2\2\t\u00d4"+
+ "\3\2\2\2\13\u00da\3\2\2\2\r\u00dc\3\2\2\2\17\u00e4\3\2\2\2\21\u00e6\3"+
+ "\2\2\2\23\u00e8\3\2\2\2\25\u00ea\3\2\2\2\27\u00ec\3\2\2\2\31\u00ee\3\2"+
+ "\2\2\33\u00f0\3\2\2\2\35\u00f2\3\2\2\2\37\u00f6\3\2\2\2!\u00f9\3\2\2\2"+
+ "#\u00fc\3\2\2\2%\u00fe\3\2\2\2\'\u0101\3\2\2\2)\u0106\3\2\2\2+\u010d\3"+
+ "\2\2\2-\u0115\3\2\2\2/\u011b\3\2\2\2\61\u011e\3\2\2\2\63\u0123\3\2\2\2"+
+ "\65\u0125\3\2\2\2\67\u0127\3\2\2\29\u012c\3\2\2\2;\u012e\3\2\2\2=\u0130"+
+ "\3\2\2\2?\u0132\3\2\2\2A\u0134\3\2\2\2C\u0137\3\2\2\2E\u013a\3\2\2\2G"+
+ "\u013c\3\2\2\2I\u013f\3\2\2\2K\u0142\3\2\2\2M\u0144\3\2\2\2O\u0146\3\2"+
+ "\2\2Q\u0149\3\2\2\2S\u014f\3\2\2\2U\u0151\3\2\2\2W\u015c\3\2\2\2Y\u015e"+
+ "\3\2\2\2[\u0167\3\2\2\2]\u0169\3\2\2\2_\u016d\3\2\2\2a\u0171\3\2\2\2c"+
+ "\u0175\3\2\2\2e\u0179\3\2\2\2g\u0185\3\2\2\2i\u0187\3\2\2\2k\u0193\3\2"+
+ "\2\2m\u0195\3\2\2\2o\u0199\3\2\2\2q\u019c\3\2\2\2s\u01a0\3\2\2\2u\u01a4"+
+ "\3\2\2\2w\u01ae\3\2\2\2y\u01b2\3\2\2\2{\u01b4\3\2\2\2}\u01ba\3\2\2\2\177"+
+ "\u01c4\3\2\2\2\u0081\u01c8\3\2\2\2\u0083\u01ca\3\2\2\2\u0085\u01ce\3\2"+
+ "\2\2\u0087\u01d8\3\2\2\2\u0089\u01dc\3\2\2\2\u008b\u01e0\3\2\2\2\u008d"+
+ "\u01fd\3\2\2\2\u008f\u01ff\3\2\2\2\u0091\u0202\3\2\2\2\u0093\u0205\3\2"+
+ "\2\2\u0095\u0209\3\2\2\2\u0097\u020b\3\2\2\2\u0099\u020d\3\2\2\2\u009b"+
+ "\u021d\3\2\2\2\u009d\u021f\3\2\2\2\u009f\u0222\3\2\2\2\u00a1\u022d\3\2"+
+ "\2\2\u00a3\u0237\3\2\2\2\u00a5\u0239\3\2\2\2\u00a7\u023b\3\2\2\2\u00a9"+
+ "\u0244\3\2\2\2\u00ab\u024b\3\2\2\2\u00ad\u0251\3\2\2\2\u00af\u0255\3\2"+
+ "\2\2\u00b1\u025b\3\2\2\2\u00b3\u0268\3\2\2\2\u00b5\u026a\3\2\2\2\u00b7"+
+ "\u0271\3\2\2\2\u00b9\u0273\3\2\2\2\u00bb\u0278\3\2\2\2\u00bd\u0285\3\2"+
+ "\2\2\u00bf\u028d\3\2\2\2\u00c1\u0290\3\2\2\2\u00c3\u0296\3\2\2\2\u00c5"+
+ "\u02a8\3\2\2\2\u00c7\u0356\3\2\2\2\u00c9\u00ca\7n\2\2\u00ca\u00cb\7q\2"+
+ "\2\u00cb\u00cc\7p\2\2\u00cc\u00cd\7i\2\2\u00cd\4\3\2\2\2\u00ce\u00cf\7"+
+ "@\2\2\u00cf\u00d0\7@\2\2\u00d0\u00d1\7@\2\2\u00d1\6\3\2\2\2\u00d2\u00d3"+
+ "\7_\2\2\u00d3\b\3\2\2\2\u00d4\u00d5\7u\2\2\u00d5\u00d6\7j\2\2\u00d6\u00d7"+
+ "\7q\2\2\u00d7\u00d8\7t\2\2\u00d8\u00d9\7v\2\2\u00d9\n\3\2\2\2\u00da\u00db"+
+ "\7(\2\2\u00db\f\3\2\2\2\u00dc\u00dd\7f\2\2\u00dd\u00de\7g\2\2\u00de\u00df"+
+ "\7h\2\2\u00df\u00e0\7c\2\2\u00e0\u00e1\7w\2\2\u00e1\u00e2\7n\2\2\u00e2"+
+ "\u00e3\7v\2\2\u00e3\16\3\2\2\2\u00e4\u00e5\7.\2\2\u00e5\20\3\2\2\2\u00e6"+
+ "\u00e7\7,\2\2\u00e7\22\3\2\2\2\u00e8\u00e9\7]\2\2\u00e9\24\3\2\2\2\u00ea"+
+ "\u00eb\7/\2\2\u00eb\26\3\2\2\2\u00ec\u00ed\7*\2\2\u00ed\30\3\2\2\2\u00ee"+
+ "\u00ef\7<\2\2\u00ef\32\3\2\2\2\u00f0\u00f1\7>\2\2\u00f1\34\3\2\2\2\u00f2"+
+ "\u00f3\7k\2\2\u00f3\u00f4\7p\2\2\u00f4\u00f5\7v\2\2\u00f5\36\3\2\2\2\u00f6"+
+ "\u00f7\7#\2\2\u00f7\u00f8\7?\2\2\u00f8 \3\2\2\2\u00f9\u00fa\7>\2\2\u00fa"+
+ "\u00fb\7?\2\2\u00fb\"\3\2\2\2\u00fc\u00fd\7A\2\2\u00fd$\3\2\2\2\u00fe"+
+ "\u00ff\7>\2\2\u00ff\u0100\7>\2\2\u0100&\3\2\2\2\u0101\u0102\7x\2\2\u0102"+
+ "\u0103\7q\2\2\u0103\u0104\7k\2\2\u0104\u0105\7f\2\2\u0105(\3\2\2\2\u0106"+
+ "\u0107\7f\2\2\u0107\u0108\7q\2\2\u0108\u0109\7w\2\2\u0109\u010a\7d\2\2"+
+ "\u010a\u010b\7n\2\2\u010b\u010c\7g\2\2\u010c*\3\2\2\2\u010d\u010e\7d\2"+
+ "\2\u010e\u010f\7q\2\2\u010f\u0110\7q\2\2\u0110\u0111\7n\2\2\u0111\u0112"+
+ "\7g\2\2\u0112\u0113\7c\2\2\u0113\u0114\7p\2\2\u0114,\3\2\2\2\u0115\u0116"+
+ "\7h\2\2\u0116\u0117\7n\2\2\u0117\u0118\7q\2\2\u0118\u0119\7c\2\2\u0119"+
+ "\u011a\7v\2\2\u011a.\3\2\2\2\u011b\u011c\7@\2\2\u011c\u011d\7@\2\2\u011d"+
+ "\60\3\2\2\2\u011e\u011f\7e\2\2\u011f\u0120\7j\2\2\u0120\u0121\7c\2\2\u0121"+
+ "\u0122\7t\2\2\u0122\62\3\2\2\2\u0123\u0124\7\'\2\2\u0124\64\3\2\2\2\u0125"+
+ "\u0126\7`\2\2\u0126\66\3\2\2\2\u0127\u0128\7d\2\2\u0128\u0129\7{\2\2\u0129"+
+ "\u012a\7v\2\2\u012a\u012b\7g\2\2\u012b8\3\2\2\2\u012c\u012d\7+\2\2\u012d"+
+ ":\3\2\2\2\u012e\u012f\7\60\2\2\u012f<\3\2\2\2\u0130\u0131\7-\2\2\u0131"+
+ ">\3\2\2\2\u0132\u0133\7?\2\2\u0133@\3\2\2\2\u0134\u0135\7(\2\2\u0135\u0136"+
+ "\7(\2\2\u0136B\3\2\2\2\u0137\u0138\7~\2\2\u0138\u0139\7~\2\2\u0139D\3"+
+ "\2\2\2\u013a\u013b\7@\2\2\u013bF\3\2\2\2\u013c\u013d\7A\2\2\u013d\u013e"+
+ "\7A\2\2\u013eH\3\2\2\2\u013f\u0140\7?\2\2\u0140\u0141\7?\2\2\u0141J\3"+
+ "\2\2\2\u0142\u0143\7\61\2\2\u0143L\3\2\2\2\u0144\u0145\7\u0080\2\2\u0145"+
+ "N\3\2\2\2\u0146\u0147\7@\2\2\u0147\u0148\7?\2\2\u0148P\3\2\2\2\u0149\u014a"+
+ "\7e\2\2\u014a\u014b\7n\2\2\u014b\u014c\7c\2\2\u014c\u014d\7u\2\2\u014d"+
+ "\u014e\7u\2\2\u014eR\3\2\2\2\u014f\u0150\7~\2\2\u0150T\3\2\2\2\u0151\u0152"+
+ "\7k\2\2\u0152\u0153\7p\2\2\u0153\u0154\7u\2\2\u0154\u0155\7v\2\2\u0155"+
+ "\u0156\7c\2\2\u0156\u0157\7p\2\2\u0157\u0158\7e\2\2\u0158\u0159\7g\2\2"+
+ "\u0159\u015a\7q\2\2\u015a\u015b\7h\2\2\u015bV\3\2\2\2\u015c\u015d\7#\2"+
+ "\2\u015dX\3\2\2\2\u015e\u015f\7v\2\2\u015f\u0160\7j\2\2\u0160\u0161\7"+
+ "k\2\2\u0161\u0162\7u\2\2\u0162Z\3\2\2\2\u0163\u0168\5]/\2\u0164\u0168"+
+ "\5_\60\2\u0165\u0168\5a\61\2\u0166\u0168\5c\62\2\u0167\u0163\3\2\2\2\u0167"+
+ "\u0164\3\2\2\2\u0167\u0165\3\2\2\2\u0167\u0166\3\2\2\2\u0168\\\3\2\2\2"+
+ "\u0169\u016b\5g\64\2\u016a\u016c\5e\63\2\u016b\u016a\3\2\2\2\u016b\u016c"+
+ "\3\2\2\2\u016c^\3\2\2\2\u016d\u016f\5s:\2\u016e\u0170\5e\63\2\u016f\u016e"+
+ "\3\2\2\2\u016f\u0170\3\2\2\2\u0170`\3\2\2\2\u0171\u0173\5{>\2\u0172\u0174"+
+ "\5e\63\2\u0173\u0172\3\2\2\2\u0173\u0174\3\2\2\2\u0174b\3\2\2\2\u0175"+
+ "\u0177\5\u0083B\2\u0176\u0178\5e\63\2\u0177\u0176\3\2\2\2\u0177\u0178"+
+ "\3\2\2\2\u0178d\3\2\2\2\u0179\u017a\t\2\2\2\u017af\3\2\2\2\u017b\u0186"+
+ "\7\62\2\2\u017c\u0183\5m\67\2\u017d\u017f\5i\65\2\u017e\u017d\3\2\2\2"+
+ "\u017e\u017f\3\2\2\2\u017f\u0184\3\2\2\2\u0180\u0181\5q9\2\u0181\u0182"+
+ "\5i\65\2\u0182\u0184\3\2\2\2\u0183\u017e\3\2\2\2\u0183\u0180\3\2\2\2\u0184"+
+ "\u0186\3\2\2\2\u0185\u017b\3\2\2\2\u0185\u017c\3\2\2\2\u0186h\3\2\2\2"+
+ "\u0187\u018f\5k\66\2\u0188\u018a\5o8\2\u0189\u0188\3\2\2\2\u018a\u018d"+
+ "\3\2\2\2\u018b\u0189\3\2\2\2\u018b\u018c\3\2\2\2\u018c\u018e\3\2\2\2\u018d"+
+ "\u018b\3\2\2\2\u018e\u0190\5k\66\2\u018f\u018b\3\2\2\2\u018f\u0190\3\2"+
+ "\2\2\u0190j\3\2\2\2\u0191\u0194\7\62\2\2\u0192\u0194\5m\67\2\u0193\u0191"+
+ "\3\2\2\2\u0193\u0192\3\2\2\2\u0194l\3\2\2\2\u0195\u0196\t\3\2\2\u0196"+
+ "n\3\2\2\2\u0197\u019a\5k\66\2\u0198\u019a\7a\2\2\u0199\u0197\3\2\2\2\u0199"+
+ "\u0198\3\2\2\2\u019ap\3\2\2\2\u019b\u019d\7a\2\2\u019c\u019b\3\2\2\2\u019d"+
+ "\u019e\3\2\2\2\u019e\u019c\3\2\2\2\u019e\u019f\3\2\2\2\u019fr\3\2\2\2"+
+ "\u01a0\u01a1\7\62\2\2\u01a1\u01a2\t\4\2\2\u01a2\u01a3\5u;\2\u01a3t\3\2"+
+ "\2\2\u01a4\u01ac\5w<\2\u01a5\u01a7\5y=\2\u01a6\u01a5\3\2\2\2\u01a7\u01aa"+
+ "\3\2\2\2\u01a8\u01a6\3\2\2\2\u01a8\u01a9\3\2\2\2\u01a9\u01ab\3\2\2\2\u01aa"+
+ "\u01a8\3\2\2\2\u01ab\u01ad\5w<\2\u01ac\u01a8\3\2\2\2\u01ac\u01ad\3\2\2"+
+ "\2\u01adv\3\2\2\2\u01ae\u01af\t\5\2\2\u01afx\3\2\2\2\u01b0\u01b3\5w<\2"+
+ "\u01b1\u01b3\7a\2\2\u01b2\u01b0\3\2\2\2\u01b2\u01b1\3\2\2\2\u01b3z\3\2"+
+ "\2\2\u01b4\u01b6\7\62\2\2\u01b5\u01b7\5q9\2\u01b6\u01b5\3\2\2\2\u01b6"+
+ "\u01b7\3\2\2\2\u01b7\u01b8\3\2\2\2\u01b8\u01b9\5}?\2\u01b9|\3\2\2\2\u01ba"+
+ "\u01c2\5\177@\2\u01bb\u01bd\5\u0081A\2\u01bc\u01bb\3\2\2\2\u01bd\u01c0"+
+ "\3\2\2\2\u01be\u01bc\3\2\2\2\u01be\u01bf\3\2\2\2\u01bf\u01c1\3\2\2\2\u01c0"+
+ "\u01be\3\2\2\2\u01c1\u01c3\5\177@\2\u01c2\u01be\3\2\2\2\u01c2\u01c3\3"+
+ "\2\2\2\u01c3~\3\2\2\2\u01c4\u01c5\t\6\2\2\u01c5\u0080\3\2\2\2\u01c6\u01c9"+
+ "\5\177@\2\u01c7\u01c9\7a\2\2\u01c8\u01c6\3\2\2\2\u01c8\u01c7\3\2\2\2\u01c9"+
+ "\u0082\3\2\2\2\u01ca\u01cb\7\62\2\2\u01cb\u01cc\t\7\2\2\u01cc\u01cd\5"+
+ "\u0085C\2\u01cd\u0084\3\2\2\2\u01ce\u01d6\5\u0087D\2\u01cf\u01d1\5\u0089"+
+ "E\2\u01d0\u01cf\3\2\2\2\u01d1\u01d4\3\2\2\2\u01d2\u01d0\3\2\2\2\u01d2"+
+ "\u01d3\3\2\2\2\u01d3\u01d5\3\2\2\2\u01d4\u01d2\3\2\2\2\u01d5\u01d7\5\u0087"+
+ "D\2\u01d6\u01d2\3\2\2\2\u01d6\u01d7\3\2\2\2\u01d7\u0086\3\2\2\2\u01d8"+
+ "\u01d9\t\b\2\2\u01d9\u0088\3\2\2\2\u01da\u01dd\5\u0087D\2\u01db\u01dd"+
+ "\7a\2\2\u01dc\u01da\3\2\2\2\u01dc\u01db\3\2\2\2\u01dd\u008a\3\2\2\2\u01de"+
+ "\u01e1\5\u008dG\2\u01df\u01e1\5\u0099M\2\u01e0\u01de\3\2\2\2\u01e0\u01df"+
+ "\3\2\2\2\u01e1\u008c\3\2\2\2\u01e2\u01e3\5i\65\2\u01e3\u01e5\7\60\2\2"+
+ "\u01e4\u01e6\5i\65\2\u01e5\u01e4\3\2\2\2\u01e5\u01e6\3\2\2\2\u01e6\u01e8"+
+ "\3\2\2\2\u01e7\u01e9\5\u008fH\2\u01e8\u01e7\3\2\2\2\u01e8\u01e9\3\2\2"+
+ "\2\u01e9\u01eb\3\2\2\2\u01ea\u01ec\5\u0097L\2\u01eb\u01ea\3\2\2\2\u01eb"+
+ "\u01ec\3\2\2\2\u01ec\u01fe\3\2\2\2\u01ed\u01ee\7\60\2\2\u01ee\u01f0\5"+
+ "i\65\2\u01ef\u01f1\5\u008fH\2\u01f0\u01ef\3\2\2\2\u01f0\u01f1\3\2\2\2"+
+ "\u01f1\u01f3\3\2\2\2\u01f2\u01f4\5\u0097L\2\u01f3\u01f2\3\2\2\2\u01f3"+
+ "\u01f4\3\2\2\2\u01f4\u01fe\3\2\2\2\u01f5\u01f6\5i\65\2\u01f6\u01f8\5\u008f"+
+ "H\2\u01f7\u01f9\5\u0097L\2\u01f8\u01f7\3\2\2\2\u01f8\u01f9\3\2\2\2\u01f9"+
+ "\u01fe\3\2\2\2\u01fa\u01fb\5i\65\2\u01fb\u01fc\5\u0097L\2\u01fc\u01fe"+
+ "\3\2\2\2\u01fd\u01e2\3\2\2\2\u01fd\u01ed\3\2\2\2\u01fd\u01f5\3\2\2\2\u01fd"+
+ "\u01fa\3\2\2\2\u01fe\u008e\3\2\2\2\u01ff\u0200\5\u0091I\2\u0200\u0201"+
+ "\5\u0093J\2\u0201\u0090\3\2\2\2\u0202\u0203\t\t\2\2\u0203\u0092\3\2\2"+
+ "\2\u0204\u0206\5\u0095K\2\u0205\u0204\3\2\2\2\u0205\u0206\3\2\2\2\u0206"+
+ "\u0207\3\2\2\2\u0207\u0208\5i\65\2\u0208\u0094\3\2\2\2\u0209\u020a\t\n"+
+ "\2\2\u020a\u0096\3\2\2\2\u020b\u020c\t\13\2\2\u020c\u0098\3\2\2\2\u020d"+
+ "\u020e\5\u009bN\2\u020e\u0210\5\u009dO\2\u020f\u0211\5\u0097L\2\u0210"+
+ "\u020f\3\2\2\2\u0210\u0211\3\2\2\2\u0211\u009a\3\2\2\2\u0212\u0214\5s"+
+ ":\2\u0213\u0215\7\60\2\2\u0214\u0213\3\2\2\2\u0214\u0215\3\2\2\2\u0215"+
+ "\u021e\3\2\2\2\u0216\u0217\7\62\2\2\u0217\u0219\t\4\2\2\u0218\u021a\5"+
+ "u;\2\u0219\u0218\3\2\2\2\u0219\u021a\3\2\2\2\u021a\u021b\3\2\2\2\u021b"+
+ "\u021c\7\60\2\2\u021c\u021e\5u;\2\u021d\u0212\3\2\2\2\u021d\u0216\3\2"+
+ "\2\2\u021e\u009c\3\2\2\2\u021f\u0220\5\u009fP\2\u0220\u0221\5\u0093J\2"+
+ "\u0221\u009e\3\2\2\2\u0222\u0223\t\f\2\2\u0223\u00a0\3\2\2\2\u0224\u0225"+
+ "\7v\2\2\u0225\u0226\7t\2\2\u0226\u0227\7w\2\2\u0227\u022e\7g\2\2\u0228"+
+ "\u0229\7h\2\2\u0229\u022a\7c\2\2\u022a\u022b\7n\2\2\u022b\u022c\7u\2\2"+
+ "\u022c\u022e\7g\2\2\u022d\u0224\3\2\2\2\u022d\u0228\3\2\2\2\u022e\u00a2"+
+ "\3\2\2\2\u022f\u0230\7)\2\2\u0230\u0231\5\u00a5S\2\u0231\u0232\7)\2\2"+
+ "\u0232\u0238\3\2\2\2\u0233\u0234\7)\2\2\u0234\u0235\5\u00b1Y\2\u0235\u0236"+
+ "\7)\2\2\u0236\u0238\3\2\2\2\u0237\u022f\3\2\2\2\u0237\u0233\3\2\2\2\u0238"+
+ "\u00a4\3\2\2\2\u0239\u023a\n\r\2\2\u023a\u00a6\3\2\2\2\u023b\u023f\7b"+
+ "\2\2\u023c\u023e\5\u00afX\2\u023d\u023c\3\2\2\2\u023e\u0241\3\2\2\2\u023f"+
+ "\u023d\3\2\2\2\u023f\u0240\3\2\2\2\u0240\u0242\3\2\2\2\u0241\u023f\3\2"+
+ "\2\2\u0242\u0243\7b\2\2\u0243\u00a8\3\2\2\2\u0244\u0246\7$\2\2\u0245\u0247"+
+ "\5\u00abV\2\u0246\u0245\3\2\2\2\u0246\u0247\3\2\2\2\u0247\u0248\3\2\2"+
+ "\2\u0248\u0249\7$\2\2\u0249\u00aa\3\2\2\2\u024a\u024c\5\u00adW\2\u024b"+
+ "\u024a\3\2\2\2\u024c\u024d\3\2\2\2\u024d\u024b\3\2\2\2\u024d\u024e\3\2"+
+ "\2\2\u024e\u00ac\3\2\2\2\u024f\u0252\n\16\2\2\u0250\u0252\5\u00b1Y\2\u0251"+
+ "\u024f\3\2\2\2\u0251\u0250\3\2\2\2\u0252\u00ae\3\2\2\2\u0253\u0256\n\17"+
+ "\2\2\u0254\u0256\5\u00b1Y\2\u0255\u0253\3\2\2\2\u0255\u0254\3\2\2\2\u0256"+
+ "\u00b0\3\2\2\2\u0257\u0258\7^\2\2\u0258\u025c\t\20\2\2\u0259\u025c\5\u00b3"+
+ "Z\2\u025a\u025c\5\u00b5[\2\u025b\u0257\3\2\2\2\u025b\u0259\3\2\2\2\u025b"+
+ "\u025a\3\2\2\2\u025c\u00b2\3\2\2\2\u025d\u025e\7^\2\2\u025e\u0269\5\177"+
+ "@\2\u025f\u0260\7^\2\2\u0260\u0261\5\177@\2\u0261\u0262\5\177@\2\u0262"+
+ "\u0269\3\2\2\2\u0263\u0264\7^\2\2\u0264\u0265\5\u00b7\\\2\u0265\u0266"+
+ "\5\177@\2\u0266\u0267\5\177@\2\u0267\u0269\3\2\2\2\u0268\u025d\3\2\2\2"+
+ "\u0268\u025f\3\2\2\2\u0268\u0263\3\2\2\2\u0269\u00b4\3\2\2\2\u026a\u026b"+
+ "\7^\2\2\u026b\u026c\7w\2\2\u026c\u026d\5w<\2\u026d\u026e\5w<\2\u026e\u026f"+
+ "\5w<\2\u026f\u0270\5w<\2\u0270\u00b6\3\2\2\2\u0271\u0272\t\21\2\2\u0272"+
+ "\u00b8\3\2\2\2\u0273\u0274\7p\2\2\u0274\u0275\7w\2\2\u0275\u0276\7n\2"+
+ "\2\u0276\u0277\7n\2\2\u0277\u00ba\3\2\2\2\u0278\u027c\5\u00bd_\2\u0279"+
+ "\u027b\5\u00bf`\2\u027a\u0279\3\2\2\2\u027b\u027e\3\2\2\2\u027c\u027a"+
+ "\3\2\2\2\u027c\u027d\3\2\2\2\u027d\u00bc\3\2\2\2\u027e\u027c\3\2\2\2\u027f"+
+ "\u0286\t\22\2\2\u0280\u0281\n\23\2\2\u0281\u0286\6_\2\2\u0282\u0283\t"+
+ "\24\2\2\u0283\u0284\t\25\2\2\u0284\u0286\6_\3\2\u0285\u027f\3\2\2\2\u0285"+
+ "\u0280\3\2\2\2\u0285\u0282\3\2\2\2\u0286\u00be\3\2\2\2\u0287\u028e\t\26"+
+ "\2\2\u0288\u0289\n\23\2\2\u0289\u028e\6`\4\2\u028a\u028b\t\24\2\2\u028b"+
+ "\u028c\t\25\2\2\u028c\u028e\6`\5\2\u028d\u0287\3\2\2\2\u028d\u0288\3\2"+
+ "\2\2\u028d\u028a\3\2\2\2\u028e\u00c0\3\2\2\2\u028f\u0291\t\27\2\2\u0290"+
+ "\u028f\3\2\2\2\u0291\u0292\3\2\2\2\u0292\u0290\3\2\2\2\u0292\u0293\3\2"+
+ "\2\2\u0293\u0294\3\2\2\2\u0294\u0295\ba\2\2\u0295\u00c2\3\2\2\2\u0296"+
+ "\u029a\7B\2\2\u0297\u0298\5\u00c5c\2\u0298\u0299\7<\2\2\u0299\u029b\3"+
+ "\2\2\2\u029a\u0297\3\2\2\2\u029a\u029b\3\2\2\2\u029b\u029c\3\2\2\2\u029c"+
+ "\u029d\5\u00c7d\2\u029d\u029e\7\61\2\2\u029e\u029f\5\u00bb^\2\u029f\u00c4"+
+ "\3\2\2\2\u02a0\u02a1\7c\2\2\u02a1\u02a2\7p\2\2\u02a2\u02a3\7f\2\2\u02a3"+
+ "\u02a4\7t\2\2\u02a4\u02a5\7q\2\2\u02a5\u02a6\7k\2\2\u02a6\u02a9\7f\2\2"+
+ "\u02a7\u02a9\5\u00bb^\2\u02a8\u02a0\3\2\2\2\u02a8\u02a7\3\2\2\2\u02a9"+
+ "\u00c6\3\2\2\2\u02aa\u02ab\7c\2\2\u02ab\u02ac\7p\2\2\u02ac\u02ad\7k\2"+
+ "\2\u02ad\u0357\7o\2\2\u02ae\u02af\7c\2\2\u02af\u02b0\7p\2\2\u02b0\u02b1"+
+ "\7k\2\2\u02b1\u02b2\7o\2\2\u02b2\u02b3\7c\2\2\u02b3\u02b4\7v\2\2\u02b4"+
+ "\u02b5\7q\2\2\u02b5\u0357\7t\2\2\u02b6\u02b7\7d\2\2\u02b7\u02b8\7q\2\2"+
+ "\u02b8\u02b9\7q\2\2\u02b9\u0357\7n\2\2\u02ba\u02bb\7e\2\2\u02bb\u02bc"+
+ "\7q\2\2\u02bc\u02bd\7n\2\2\u02bd\u02be\7q\2\2\u02be\u0357\7t\2\2\u02bf"+
+ "\u02c0\7e\2\2\u02c0\u02c1\7q\2\2\u02c1\u02c2\7n\2\2\u02c2\u02c3\7q\2\2"+
+ "\u02c3\u02c4\7t\2\2\u02c4\u02c5\7U\2\2\u02c5\u02c6\7v\2\2\u02c6\u02c7"+
+ "\7c\2\2\u02c7\u02c8\7v\2\2\u02c8\u02c9\7g\2\2\u02c9\u02ca\7N\2\2\u02ca"+
+ "\u02cb\7k\2\2\u02cb\u02cc\7u\2\2\u02cc\u0357\7v\2\2\u02cd\u02ce\7f\2\2"+
+ "\u02ce\u02cf\7k\2\2\u02cf\u02d0\7o\2\2\u02d0\u02d1\7g\2\2\u02d1\u0357"+
+ "\7p\2\2\u02d2\u02d3\7f\2\2\u02d3\u02d4\7k\2\2\u02d4\u02d5\7o\2\2\u02d5"+
+ "\u02d6\7g\2\2\u02d6\u02d7\7p\2\2\u02d7\u02d8\7Q\2\2\u02d8\u02d9\7h\2\2"+
+ "\u02d9\u02da\7h\2\2\u02da\u02db\7u\2\2\u02db\u02dc\7g\2\2\u02dc\u0357"+
+ "\7v\2\2\u02dd\u02de\7f\2\2\u02de\u02df\7k\2\2\u02df\u02e0\7o\2\2\u02e0"+
+ "\u02e1\7g\2\2\u02e1\u02e2\7p\2\2\u02e2\u02e3\7U\2\2\u02e3\u02e4\7k\2\2"+
+ "\u02e4\u02e5\7|\2\2\u02e5\u0357\7g\2\2\u02e6\u02e7\7f\2\2\u02e7\u02e8"+
+ "\7t\2\2\u02e8\u02e9\7c\2\2\u02e9\u02ea\7y\2\2\u02ea\u02eb\7c\2\2\u02eb"+
+ "\u02ec\7d\2\2\u02ec\u02ed\7n\2\2\u02ed\u0357\7g\2\2\u02ee\u02ef\7h\2\2"+
+ "\u02ef\u02f0\7t\2\2\u02f0\u02f1\7c\2\2\u02f1\u02f2\7e\2\2\u02f2\u02f3"+
+ "\7v\2\2\u02f3\u02f4\7k\2\2\u02f4\u02f5\7q\2\2\u02f5\u0357\7p\2\2\u02f6"+
+ "\u02f7\7k\2\2\u02f7\u0357\7f\2\2\u02f8\u02f9\7k\2\2\u02f9\u02fa\7p\2\2"+
+ "\u02fa\u02fb\7v\2\2\u02fb\u02fc\7g\2\2\u02fc\u02fd\7i\2\2\u02fd\u02fe"+
+ "\7g\2\2\u02fe\u0357\7t\2\2\u02ff\u0300\7k\2\2\u0300\u0301\7p\2\2\u0301"+
+ "\u0302\7v\2\2\u0302\u0303\7C\2\2\u0303\u0304\7t\2\2\u0304\u0305\7t\2\2"+
+ "\u0305\u0306\7c\2\2\u0306\u0357\7{\2\2\u0307\u0308\7k\2\2\u0308\u0309"+
+ "\7p\2\2\u0309\u030a\7v\2\2\u030a\u030b\7g\2\2\u030b\u030c\7t\2\2\u030c"+
+ "\u030d\7r\2\2\u030d\u030e\7q\2\2\u030e\u030f\7n\2\2\u030f\u0310\7c\2\2"+
+ "\u0310\u0311\7v\2\2\u0311\u0312\7q\2\2\u0312\u0357\7t\2\2\u0313\u0314"+
+ "\7n\2\2\u0314\u0315\7c\2\2\u0315\u0316\7{\2\2\u0316\u0317\7q\2\2\u0317"+
+ "\u0318\7w\2\2\u0318\u0357\7v\2\2\u0319\u031a\7r\2\2\u031a\u031b\7n\2\2"+
+ "\u031b\u031c\7w\2\2\u031c\u031d\7t\2\2\u031d\u031e\7c\2\2\u031e\u031f"+
+ "\7n\2\2\u031f\u0357\7u\2\2\u0320\u0321\7u\2\2\u0321\u0322\7v\2\2\u0322"+
+ "\u0323\7c\2\2\u0323\u0324\7v\2\2\u0324\u0325\7g\2\2\u0325\u0326\7N\2\2"+
+ "\u0326\u0327\7k\2\2\u0327\u0328\7u\2\2\u0328\u0329\7v\2\2\u0329\u032a"+
+ "\7C\2\2\u032a\u032b\7p\2\2\u032b\u032c\7k\2\2\u032c\u032d\7o\2\2\u032d"+
+ "\u032e\7c\2\2\u032e\u032f\7v\2\2\u032f\u0330\7q\2\2\u0330\u0357\7t\2\2"+
+ "\u0331\u0332\7u\2\2\u0332\u0333\7v\2\2\u0333\u0334\7t\2\2\u0334\u0335"+
+ "\7k\2\2\u0335\u0336\7p\2\2\u0336\u0357\7i\2\2\u0337\u0338\7u\2\2\u0338"+
+ "\u0339\7v\2\2\u0339\u033a\7t\2\2\u033a\u033b\7k\2\2\u033b\u033c\7p\2\2"+
+ "\u033c\u033d\7i\2\2\u033d\u033e\7C\2\2\u033e\u033f\7t\2\2\u033f\u0340"+
+ "\7t\2\2\u0340\u0341\7c\2\2\u0341\u0357\7{\2\2\u0342\u0343\7v\2\2\u0343"+
+ "\u0344\7t\2\2\u0344\u0345\7c\2\2\u0345\u0346\7p\2\2\u0346\u0347\7u\2\2"+
+ "\u0347\u0348\7k\2\2\u0348\u0349\7v\2\2\u0349\u034a\7k\2\2\u034a\u034b"+
+ "\7q\2\2\u034b\u0357\7p\2\2\u034c\u034d\7v\2\2\u034d\u034e\7{\2\2\u034e"+
+ "\u034f\7r\2\2\u034f\u0350\7g\2\2\u0350\u0351\7f\2\2\u0351\u0352\7C\2\2"+
+ "\u0352\u0353\7t\2\2\u0353\u0354\7t\2\2\u0354\u0355\7c\2\2\u0355\u0357"+
+ "\7{\2\2\u0356\u02aa\3\2\2\2\u0356\u02ae\3\2\2\2\u0356\u02b6\3\2\2\2\u0356"+
+ "\u02ba\3\2\2\2\u0356\u02bf\3\2\2\2\u0356\u02cd\3\2\2\2\u0356\u02d2\3\2"+
+ "\2\2\u0356\u02dd\3\2\2\2\u0356\u02e6\3\2\2\2\u0356\u02ee\3\2\2\2\u0356"+
+ "\u02f6\3\2\2\2\u0356\u02f8\3\2\2\2\u0356\u02ff\3\2\2\2\u0356\u0307\3\2"+
+ "\2\2\u0356\u0313\3\2\2\2\u0356\u0319\3\2\2\2\u0356\u0320\3\2\2\2\u0356"+
+ "\u0331\3\2\2\2\u0356\u0337\3\2\2\2\u0356\u0342\3\2\2\2\u0356\u034c\3\2"+
+ "\2\2\u0357\u00c8\3\2\2\2\67\2\u0167\u016b\u016f\u0173\u0177\u017e\u0183"+
+ "\u0185\u018b\u018f\u0193\u0199\u019e\u01a8\u01ac\u01b2\u01b6\u01be\u01c2"+
+ "\u01c8\u01d2\u01d6\u01dc\u01e0\u01e5\u01e8\u01eb\u01f0\u01f3\u01f8\u01fd"+
+ "\u0205\u0210\u0214\u0219\u021d\u022d\u0237\u023f\u0246\u024d\u0251\u0255"+
+ "\u025b\u0268\u027c\u0285\u028d\u0292\u029a\u02a8\u0356\3\b\2\2";
+ public static final ATN _ATN =
+ new ATNDeserializer().deserialize(_serializedATN.toCharArray());
+ static {
+ }
+}
\ No newline at end of file
diff --git a/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionLexer.tokens b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionLexer.tokens
new file mode 100644
index 0000000..d379280
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionLexer.tokens
@@ -0,0 +1,101 @@
+NullLiteral=51
+T__29=14
+T__28=15
+T__27=16
+T__26=17
+T__25=18
+T__24=19
+T__23=20
+T__22=21
+CharacterLiteral=48
+T__21=22
+T__20=23
+SingleQuoteString=49
+T__9=34
+T__8=35
+Identifier=52
+T__7=36
+T__6=37
+T__5=38
+T__4=39
+T__19=24
+T__16=27
+T__15=28
+T__18=25
+T__17=26
+T__12=31
+T__11=32
+T__14=29
+T__13=30
+T__10=33
+THIS=44
+PackageName=55
+DoubleQuoteString=50
+T__42=1
+T__40=3
+T__41=2
+ResourceType=56
+T__30=13
+T__31=12
+T__32=11
+WS=53
+T__33=10
+T__34=9
+T__35=8
+T__36=7
+T__37=6
+T__38=5
+T__39=4
+T__1=42
+T__0=43
+FloatingPointLiteral=46
+T__3=40
+T__2=41
+IntegerLiteral=45
+ResourceReference=54
+BooleanLiteral=47
+'!'=43
+'instanceof'=42
+'|'=41
+'class'=40
+'>='=39
+'~'=38
+'/'=37
+'=='=36
+'??'=35
+'null'=51
+'>'=34
+'||'=33
+'this'=44
+'&&'=32
+'='=31
+'+'=30
+'.'=29
+')'=28
+'byte'=27
+'^'=26
+'%'=25
+'>>'=23
+'char'=24
+'float'=22
+'boolean'=21
+'double'=20
+'<<'=18
+'void'=19
+'?'=17
+'<='=16
+'!='=15
+'<'=13
+'int'=14
+':'=12
+'('=11
+'-'=10
+'['=9
+'*'=8
+','=7
+'default'=6
+'&'=5
+'short'=4
+']'=3
+'>>>'=2
+'long'=1
diff --git a/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionListener.java b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionListener.java
new file mode 100644
index 0000000..020be83
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionListener.java
@@ -0,0 +1,428 @@
+// Generated from BindingExpression.g4 by ANTLR 4.4
+package android.databinding.parser;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.misc.NotNull;
+import org.antlr.v4.runtime.tree.ParseTreeListener;
+
+/**
+ * This interface defines a complete listener for a parse tree produced by
+ * {@link BindingExpressionParser}.
+ */
+public interface BindingExpressionListener extends ParseTreeListener {
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterExpression(@NotNull BindingExpressionParser.ExpressionContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitExpression(@NotNull BindingExpressionParser.ExpressionContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#resources}.
+ * @param ctx the parse tree
+ */
+ void enterResources(@NotNull BindingExpressionParser.ResourcesContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#resources}.
+ * @param ctx the parse tree
+ */
+ void exitResources(@NotNull BindingExpressionParser.ResourcesContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code BracketOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterBracketOp(@NotNull BindingExpressionParser.BracketOpContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code BracketOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitBracketOp(@NotNull BindingExpressionParser.BracketOpContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code UnaryOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterUnaryOp(@NotNull BindingExpressionParser.UnaryOpContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code UnaryOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitUnaryOp(@NotNull BindingExpressionParser.UnaryOpContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code CastOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterCastOp(@NotNull BindingExpressionParser.CastOpContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code CastOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitCastOp(@NotNull BindingExpressionParser.CastOpContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#resourceParameters}.
+ * @param ctx the parse tree
+ */
+ void enterResourceParameters(@NotNull BindingExpressionParser.ResourceParametersContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#resourceParameters}.
+ * @param ctx the parse tree
+ */
+ void exitResourceParameters(@NotNull BindingExpressionParser.ResourceParametersContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code AndOrOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterAndOrOp(@NotNull BindingExpressionParser.AndOrOpContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code AndOrOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitAndOrOp(@NotNull BindingExpressionParser.AndOrOpContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code MethodInvocation}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterMethodInvocation(@NotNull BindingExpressionParser.MethodInvocationContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code MethodInvocation}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitMethodInvocation(@NotNull BindingExpressionParser.MethodInvocationContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#expressionList}.
+ * @param ctx the parse tree
+ */
+ void enterExpressionList(@NotNull BindingExpressionParser.ExpressionListContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#expressionList}.
+ * @param ctx the parse tree
+ */
+ void exitExpressionList(@NotNull BindingExpressionParser.ExpressionListContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#classOrInterfaceType}.
+ * @param ctx the parse tree
+ */
+ void enterClassOrInterfaceType(@NotNull BindingExpressionParser.ClassOrInterfaceTypeContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#classOrInterfaceType}.
+ * @param ctx the parse tree
+ */
+ void exitClassOrInterfaceType(@NotNull BindingExpressionParser.ClassOrInterfaceTypeContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#stringLiteral}.
+ * @param ctx the parse tree
+ */
+ void enterStringLiteral(@NotNull BindingExpressionParser.StringLiteralContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#stringLiteral}.
+ * @param ctx the parse tree
+ */
+ void exitStringLiteral(@NotNull BindingExpressionParser.StringLiteralContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code Primary}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterPrimary(@NotNull BindingExpressionParser.PrimaryContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code Primary}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitPrimary(@NotNull BindingExpressionParser.PrimaryContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#type}.
+ * @param ctx the parse tree
+ */
+ void enterType(@NotNull BindingExpressionParser.TypeContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#type}.
+ * @param ctx the parse tree
+ */
+ void exitType(@NotNull BindingExpressionParser.TypeContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#bindingSyntax}.
+ * @param ctx the parse tree
+ */
+ void enterBindingSyntax(@NotNull BindingExpressionParser.BindingSyntaxContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#bindingSyntax}.
+ * @param ctx the parse tree
+ */
+ void exitBindingSyntax(@NotNull BindingExpressionParser.BindingSyntaxContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code ComparisonOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterComparisonOp(@NotNull BindingExpressionParser.ComparisonOpContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code ComparisonOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitComparisonOp(@NotNull BindingExpressionParser.ComparisonOpContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code TernaryOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterTernaryOp(@NotNull BindingExpressionParser.TernaryOpContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code TernaryOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitTernaryOp(@NotNull BindingExpressionParser.TernaryOpContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#constantValue}.
+ * @param ctx the parse tree
+ */
+ void enterConstantValue(@NotNull BindingExpressionParser.ConstantValueContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#constantValue}.
+ * @param ctx the parse tree
+ */
+ void exitConstantValue(@NotNull BindingExpressionParser.ConstantValueContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code DotOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterDotOp(@NotNull BindingExpressionParser.DotOpContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code DotOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitDotOp(@NotNull BindingExpressionParser.DotOpContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#defaults}.
+ * @param ctx the parse tree
+ */
+ void enterDefaults(@NotNull BindingExpressionParser.DefaultsContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#defaults}.
+ * @param ctx the parse tree
+ */
+ void exitDefaults(@NotNull BindingExpressionParser.DefaultsContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code BitShiftOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterBitShiftOp(@NotNull BindingExpressionParser.BitShiftOpContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code BitShiftOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitBitShiftOp(@NotNull BindingExpressionParser.BitShiftOpContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code InstanceOfOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterInstanceOfOp(@NotNull BindingExpressionParser.InstanceOfOpContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code InstanceOfOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitInstanceOfOp(@NotNull BindingExpressionParser.InstanceOfOpContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code BinaryOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterBinaryOp(@NotNull BindingExpressionParser.BinaryOpContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code BinaryOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitBinaryOp(@NotNull BindingExpressionParser.BinaryOpContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#explicitGenericInvocation}.
+ * @param ctx the parse tree
+ */
+ void enterExplicitGenericInvocation(@NotNull BindingExpressionParser.ExplicitGenericInvocationContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#explicitGenericInvocation}.
+ * @param ctx the parse tree
+ */
+ void exitExplicitGenericInvocation(@NotNull BindingExpressionParser.ExplicitGenericInvocationContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code Resource}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterResource(@NotNull BindingExpressionParser.ResourceContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code Resource}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitResource(@NotNull BindingExpressionParser.ResourceContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#typeArguments}.
+ * @param ctx the parse tree
+ */
+ void enterTypeArguments(@NotNull BindingExpressionParser.TypeArgumentsContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#typeArguments}.
+ * @param ctx the parse tree
+ */
+ void exitTypeArguments(@NotNull BindingExpressionParser.TypeArgumentsContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code Grouping}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterGrouping(@NotNull BindingExpressionParser.GroupingContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code Grouping}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitGrouping(@NotNull BindingExpressionParser.GroupingContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code MathOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterMathOp(@NotNull BindingExpressionParser.MathOpContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code MathOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitMathOp(@NotNull BindingExpressionParser.MathOpContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#classExtraction}.
+ * @param ctx the parse tree
+ */
+ void enterClassExtraction(@NotNull BindingExpressionParser.ClassExtractionContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#classExtraction}.
+ * @param ctx the parse tree
+ */
+ void exitClassExtraction(@NotNull BindingExpressionParser.ClassExtractionContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#arguments}.
+ * @param ctx the parse tree
+ */
+ void enterArguments(@NotNull BindingExpressionParser.ArgumentsContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#arguments}.
+ * @param ctx the parse tree
+ */
+ void exitArguments(@NotNull BindingExpressionParser.ArgumentsContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#primitiveType}.
+ * @param ctx the parse tree
+ */
+ void enterPrimitiveType(@NotNull BindingExpressionParser.PrimitiveTypeContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#primitiveType}.
+ * @param ctx the parse tree
+ */
+ void exitPrimitiveType(@NotNull BindingExpressionParser.PrimitiveTypeContext ctx);
+
+ /**
+ * Enter a parse tree produced by the {@code QuestionQuestionOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void enterQuestionQuestionOp(@NotNull BindingExpressionParser.QuestionQuestionOpContext ctx);
+ /**
+ * Exit a parse tree produced by the {@code QuestionQuestionOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ */
+ void exitQuestionQuestionOp(@NotNull BindingExpressionParser.QuestionQuestionOpContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#javaLiteral}.
+ * @param ctx the parse tree
+ */
+ void enterJavaLiteral(@NotNull BindingExpressionParser.JavaLiteralContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#javaLiteral}.
+ * @param ctx the parse tree
+ */
+ void exitJavaLiteral(@NotNull BindingExpressionParser.JavaLiteralContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#explicitGenericInvocationSuffix}.
+ * @param ctx the parse tree
+ */
+ void enterExplicitGenericInvocationSuffix(@NotNull BindingExpressionParser.ExplicitGenericInvocationSuffixContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#explicitGenericInvocationSuffix}.
+ * @param ctx the parse tree
+ */
+ void exitExplicitGenericInvocationSuffix(@NotNull BindingExpressionParser.ExplicitGenericInvocationSuffixContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#identifier}.
+ * @param ctx the parse tree
+ */
+ void enterIdentifier(@NotNull BindingExpressionParser.IdentifierContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#identifier}.
+ * @param ctx the parse tree
+ */
+ void exitIdentifier(@NotNull BindingExpressionParser.IdentifierContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link BindingExpressionParser#literal}.
+ * @param ctx the parse tree
+ */
+ void enterLiteral(@NotNull BindingExpressionParser.LiteralContext ctx);
+ /**
+ * Exit a parse tree produced by {@link BindingExpressionParser#literal}.
+ * @param ctx the parse tree
+ */
+ void exitLiteral(@NotNull BindingExpressionParser.LiteralContext ctx);
+}
\ No newline at end of file
diff --git a/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionParser.java b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionParser.java
new file mode 100644
index 0000000..0d41591
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionParser.java
@@ -0,0 +1,2005 @@
+// Generated from BindingExpression.g4 by ANTLR 4.4
+package android.databinding.parser;
+import org.antlr.v4.runtime.atn.*;
+import org.antlr.v4.runtime.dfa.DFA;
+import org.antlr.v4.runtime.*;
+import org.antlr.v4.runtime.misc.*;
+import org.antlr.v4.runtime.tree.*;
+import java.util.List;
+import java.util.Iterator;
+import java.util.ArrayList;
+
+public class BindingExpressionParser extends Parser {
+ public static final int
+ T__42=1, T__41=2, T__40=3, T__39=4, T__38=5, T__37=6, T__36=7, T__35=8,
+ T__34=9, T__33=10, T__32=11, T__31=12, T__30=13, T__29=14, T__28=15, T__27=16,
+ T__26=17, T__25=18, T__24=19, T__23=20, T__22=21, T__21=22, T__20=23,
+ T__19=24, T__18=25, T__17=26, T__16=27, T__15=28, T__14=29, T__13=30,
+ T__12=31, T__11=32, T__10=33, T__9=34, T__8=35, T__7=36, T__6=37, T__5=38,
+ T__4=39, T__3=40, T__2=41, T__1=42, T__0=43, THIS=44, IntegerLiteral=45,
+ FloatingPointLiteral=46, BooleanLiteral=47, CharacterLiteral=48, SingleQuoteString=49,
+ DoubleQuoteString=50, NullLiteral=51, Identifier=52, WS=53, ResourceReference=54,
+ PackageName=55, ResourceType=56;
+ public static final String[] tokenNames = {
+ "<INVALID>", "'long'", "'>>>'", "']'", "'short'", "'&'", "'default'",
+ "','", "'*'", "'['", "'-'", "'('", "':'", "'<'", "'int'", "'!='", "'<='",
+ "'?'", "'<<'", "'void'", "'double'", "'boolean'", "'float'", "'>>'", "'char'",
+ "'%'", "'^'", "'byte'", "')'", "'.'", "'+'", "'='", "'&&'", "'||'", "'>'",
+ "'??'", "'=='", "'/'", "'~'", "'>='", "'class'", "'|'", "'instanceof'",
+ "'!'", "'this'", "IntegerLiteral", "FloatingPointLiteral", "BooleanLiteral",
+ "CharacterLiteral", "SingleQuoteString", "DoubleQuoteString", "'null'",
+ "Identifier", "WS", "ResourceReference", "PackageName", "ResourceType"
+ };
+ public static final int
+ RULE_bindingSyntax = 0, RULE_defaults = 1, RULE_constantValue = 2, RULE_expression = 3,
+ RULE_classExtraction = 4, RULE_expressionList = 5, RULE_literal = 6, RULE_identifier = 7,
+ RULE_javaLiteral = 8, RULE_stringLiteral = 9, RULE_explicitGenericInvocation = 10,
+ RULE_typeArguments = 11, RULE_type = 12, RULE_explicitGenericInvocationSuffix = 13,
+ RULE_arguments = 14, RULE_classOrInterfaceType = 15, RULE_primitiveType = 16,
+ RULE_resources = 17, RULE_resourceParameters = 18;
+ public static final String[] ruleNames = {
+ "bindingSyntax", "defaults", "constantValue", "expression", "classExtraction",
+ "expressionList", "literal", "identifier", "javaLiteral", "stringLiteral",
+ "explicitGenericInvocation", "typeArguments", "type", "explicitGenericInvocationSuffix",
+ "arguments", "classOrInterfaceType", "primitiveType", "resources", "resourceParameters"
+ };
+
+ @Override
+ public String getGrammarFileName() { return "BindingExpression.g4"; }
+
+ @Override
+ public String[] getTokenNames() { return tokenNames; }
+
+ @Override
+ public String[] getRuleNames() { return ruleNames; }
+
+ @Override
+ public String getSerializedATN() { return _serializedATN; }
+
+ public BindingExpressionParser(TokenStream input) {
+ super(input);
+ _interp = new ParserATNSimulator(this,_ATN);
+ }
+ public static class BindingSyntaxContext extends ParserRuleContext {
+ public DefaultsContext defaults() {
+ return getRuleContext(DefaultsContext.class,0);
+ }
+ public ExpressionContext expression() {
+ return getRuleContext(ExpressionContext.class,0);
+ }
+ public BindingSyntaxContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_bindingSyntax; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterBindingSyntax(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitBindingSyntax(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitBindingSyntax(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final BindingSyntaxContext bindingSyntax() throws RecognitionException {
+ BindingSyntaxContext _localctx = new BindingSyntaxContext(_ctx, getState());
+ enterRule(_localctx, 0, RULE_bindingSyntax);
+ int _la;
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(38); expression(0);
+ setState(40);
+ _la = _input.LA(1);
+ if (_la==T__36) {
+ {
+ setState(39); defaults();
+ }
+ }
+
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class DefaultsContext extends ParserRuleContext {
+ public ConstantValueContext constantValue() {
+ return getRuleContext(ConstantValueContext.class,0);
+ }
+ public DefaultsContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_defaults; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterDefaults(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitDefaults(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitDefaults(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final DefaultsContext defaults() throws RecognitionException {
+ DefaultsContext _localctx = new DefaultsContext(_ctx, getState());
+ enterRule(_localctx, 2, RULE_defaults);
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(42); match(T__36);
+ setState(43); match(T__37);
+ setState(44); match(T__12);
+ setState(45); constantValue();
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class ConstantValueContext extends ParserRuleContext {
+ public LiteralContext literal() {
+ return getRuleContext(LiteralContext.class,0);
+ }
+ public IdentifierContext identifier() {
+ return getRuleContext(IdentifierContext.class,0);
+ }
+ public TerminalNode ResourceReference() { return getToken(BindingExpressionParser.ResourceReference, 0); }
+ public ConstantValueContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_constantValue; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterConstantValue(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitConstantValue(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitConstantValue(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final ConstantValueContext constantValue() throws RecognitionException {
+ ConstantValueContext _localctx = new ConstantValueContext(_ctx, getState());
+ enterRule(_localctx, 4, RULE_constantValue);
+ try {
+ setState(50);
+ switch (_input.LA(1)) {
+ case IntegerLiteral:
+ case FloatingPointLiteral:
+ case BooleanLiteral:
+ case CharacterLiteral:
+ case SingleQuoteString:
+ case DoubleQuoteString:
+ case NullLiteral:
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(47); literal();
+ }
+ break;
+ case ResourceReference:
+ enterOuterAlt(_localctx, 2);
+ {
+ setState(48); match(ResourceReference);
+ }
+ break;
+ case Identifier:
+ enterOuterAlt(_localctx, 3);
+ {
+ setState(49); identifier();
+ }
+ break;
+ default:
+ throw new NoViableAltException(this);
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class ExpressionContext extends ParserRuleContext {
+ public ExpressionContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_expression; }
+
+ public ExpressionContext() { }
+ public void copyFrom(ExpressionContext ctx) {
+ super.copyFrom(ctx);
+ }
+ }
+ public static class BracketOpContext extends ExpressionContext {
+ public ExpressionContext expression(int i) {
+ return getRuleContext(ExpressionContext.class,i);
+ }
+ public List<? extends ExpressionContext> expression() {
+ return getRuleContexts(ExpressionContext.class);
+ }
+ public BracketOpContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterBracketOp(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitBracketOp(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitBracketOp(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class ResourceContext extends ExpressionContext {
+ public ResourcesContext resources() {
+ return getRuleContext(ResourcesContext.class,0);
+ }
+ public ResourceContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterResource(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitResource(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitResource(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class CastOpContext extends ExpressionContext {
+ public TypeContext type() {
+ return getRuleContext(TypeContext.class,0);
+ }
+ public ExpressionContext expression() {
+ return getRuleContext(ExpressionContext.class,0);
+ }
+ public CastOpContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterCastOp(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitCastOp(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitCastOp(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class UnaryOpContext extends ExpressionContext {
+ public Token op;
+ public ExpressionContext expression() {
+ return getRuleContext(ExpressionContext.class,0);
+ }
+ public UnaryOpContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterUnaryOp(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitUnaryOp(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitUnaryOp(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class AndOrOpContext extends ExpressionContext {
+ public ExpressionContext left;
+ public Token op;
+ public ExpressionContext right;
+ public ExpressionContext expression(int i) {
+ return getRuleContext(ExpressionContext.class,i);
+ }
+ public List<? extends ExpressionContext> expression() {
+ return getRuleContexts(ExpressionContext.class);
+ }
+ public AndOrOpContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterAndOrOp(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitAndOrOp(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitAndOrOp(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class MethodInvocationContext extends ExpressionContext {
+ public ExpressionContext target;
+ public Token methodName;
+ public ExpressionListContext args;
+ public ExpressionListContext expressionList() {
+ return getRuleContext(ExpressionListContext.class,0);
+ }
+ public TerminalNode Identifier() { return getToken(BindingExpressionParser.Identifier, 0); }
+ public ExpressionContext expression() {
+ return getRuleContext(ExpressionContext.class,0);
+ }
+ public MethodInvocationContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterMethodInvocation(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitMethodInvocation(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitMethodInvocation(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class PrimaryContext extends ExpressionContext {
+ public ClassExtractionContext classExtraction() {
+ return getRuleContext(ClassExtractionContext.class,0);
+ }
+ public LiteralContext literal() {
+ return getRuleContext(LiteralContext.class,0);
+ }
+ public IdentifierContext identifier() {
+ return getRuleContext(IdentifierContext.class,0);
+ }
+ public PrimaryContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterPrimary(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitPrimary(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitPrimary(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class GroupingContext extends ExpressionContext {
+ public ExpressionContext expression() {
+ return getRuleContext(ExpressionContext.class,0);
+ }
+ public GroupingContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterGrouping(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitGrouping(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitGrouping(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class TernaryOpContext extends ExpressionContext {
+ public ExpressionContext left;
+ public Token op;
+ public ExpressionContext iftrue;
+ public ExpressionContext iffalse;
+ public ExpressionContext expression(int i) {
+ return getRuleContext(ExpressionContext.class,i);
+ }
+ public List<? extends ExpressionContext> expression() {
+ return getRuleContexts(ExpressionContext.class);
+ }
+ public TernaryOpContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterTernaryOp(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitTernaryOp(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitTernaryOp(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class ComparisonOpContext extends ExpressionContext {
+ public ExpressionContext left;
+ public Token op;
+ public ExpressionContext right;
+ public ExpressionContext expression(int i) {
+ return getRuleContext(ExpressionContext.class,i);
+ }
+ public List<? extends ExpressionContext> expression() {
+ return getRuleContexts(ExpressionContext.class);
+ }
+ public ComparisonOpContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterComparisonOp(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitComparisonOp(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitComparisonOp(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class DotOpContext extends ExpressionContext {
+ public TerminalNode Identifier() { return getToken(BindingExpressionParser.Identifier, 0); }
+ public ExpressionContext expression() {
+ return getRuleContext(ExpressionContext.class,0);
+ }
+ public DotOpContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterDotOp(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitDotOp(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitDotOp(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class MathOpContext extends ExpressionContext {
+ public ExpressionContext left;
+ public Token op;
+ public ExpressionContext right;
+ public ExpressionContext expression(int i) {
+ return getRuleContext(ExpressionContext.class,i);
+ }
+ public List<? extends ExpressionContext> expression() {
+ return getRuleContexts(ExpressionContext.class);
+ }
+ public MathOpContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterMathOp(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitMathOp(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitMathOp(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class BitShiftOpContext extends ExpressionContext {
+ public ExpressionContext left;
+ public Token op;
+ public ExpressionContext right;
+ public ExpressionContext expression(int i) {
+ return getRuleContext(ExpressionContext.class,i);
+ }
+ public List<? extends ExpressionContext> expression() {
+ return getRuleContexts(ExpressionContext.class);
+ }
+ public BitShiftOpContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterBitShiftOp(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitBitShiftOp(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitBitShiftOp(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class QuestionQuestionOpContext extends ExpressionContext {
+ public ExpressionContext left;
+ public Token op;
+ public ExpressionContext right;
+ public ExpressionContext expression(int i) {
+ return getRuleContext(ExpressionContext.class,i);
+ }
+ public List<? extends ExpressionContext> expression() {
+ return getRuleContexts(ExpressionContext.class);
+ }
+ public QuestionQuestionOpContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterQuestionQuestionOp(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitQuestionQuestionOp(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitQuestionQuestionOp(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class InstanceOfOpContext extends ExpressionContext {
+ public TypeContext type() {
+ return getRuleContext(TypeContext.class,0);
+ }
+ public ExpressionContext expression() {
+ return getRuleContext(ExpressionContext.class,0);
+ }
+ public InstanceOfOpContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterInstanceOfOp(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitInstanceOfOp(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitInstanceOfOp(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+ public static class BinaryOpContext extends ExpressionContext {
+ public ExpressionContext left;
+ public Token op;
+ public ExpressionContext right;
+ public ExpressionContext expression(int i) {
+ return getRuleContext(ExpressionContext.class,i);
+ }
+ public List<? extends ExpressionContext> expression() {
+ return getRuleContexts(ExpressionContext.class);
+ }
+ public BinaryOpContext(ExpressionContext ctx) { copyFrom(ctx); }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterBinaryOp(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitBinaryOp(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitBinaryOp(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final ExpressionContext expression() throws RecognitionException {
+ return expression(0);
+ }
+
+ private ExpressionContext expression(int _p) throws RecognitionException {
+ ParserRuleContext _parentctx = _ctx;
+ int _parentState = getState();
+ ExpressionContext _localctx = new ExpressionContext(_ctx, _parentState);
+ ExpressionContext _prevctx = _localctx;
+ int _startState = 6;
+ enterRecursionRule(_localctx, 6, RULE_expression, _p);
+ int _la;
+ try {
+ int _alt;
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(70);
+ switch ( getInterpreter().adaptivePredict(_input,2,_ctx) ) {
+ case 1:
+ {
+ _localctx = new CastOpContext(_localctx);
+ _ctx = _localctx;
+ _prevctx = _localctx;
+
+ setState(53); match(T__32);
+ setState(54); type();
+ setState(55); match(T__15);
+ setState(56); expression(16);
+ }
+ break;
+
+ case 2:
+ {
+ _localctx = new UnaryOpContext(_localctx);
+ _ctx = _localctx;
+ _prevctx = _localctx;
+ setState(58);
+ ((UnaryOpContext)_localctx).op = _input.LT(1);
+ _la = _input.LA(1);
+ if ( !(_la==T__33 || _la==T__13) ) {
+ ((UnaryOpContext)_localctx).op = _errHandler.recoverInline(this);
+ }
+ consume();
+ setState(59); expression(15);
+ }
+ break;
+
+ case 3:
+ {
+ _localctx = new UnaryOpContext(_localctx);
+ _ctx = _localctx;
+ _prevctx = _localctx;
+ setState(60);
+ ((UnaryOpContext)_localctx).op = _input.LT(1);
+ _la = _input.LA(1);
+ if ( !(_la==T__5 || _la==T__0) ) {
+ ((UnaryOpContext)_localctx).op = _errHandler.recoverInline(this);
+ }
+ consume();
+ setState(61); expression(14);
+ }
+ break;
+
+ case 4:
+ {
+ _localctx = new GroupingContext(_localctx);
+ _ctx = _localctx;
+ _prevctx = _localctx;
+ setState(62); match(T__32);
+ setState(63); expression(0);
+ setState(64); match(T__15);
+ }
+ break;
+
+ case 5:
+ {
+ _localctx = new PrimaryContext(_localctx);
+ _ctx = _localctx;
+ _prevctx = _localctx;
+ setState(66); literal();
+ }
+ break;
+
+ case 6:
+ {
+ _localctx = new PrimaryContext(_localctx);
+ _ctx = _localctx;
+ _prevctx = _localctx;
+ setState(67); identifier();
+ }
+ break;
+
+ case 7:
+ {
+ _localctx = new PrimaryContext(_localctx);
+ _ctx = _localctx;
+ _prevctx = _localctx;
+ setState(68); classExtraction();
+ }
+ break;
+
+ case 8:
+ {
+ _localctx = new ResourceContext(_localctx);
+ _ctx = _localctx;
+ _prevctx = _localctx;
+ setState(69); resources();
+ }
+ break;
+ }
+ _ctx.stop = _input.LT(-1);
+ setState(132);
+ _errHandler.sync(this);
+ _alt = getInterpreter().adaptivePredict(_input,5,_ctx);
+ while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
+ if ( _alt==1 ) {
+ if ( _parseListeners!=null ) triggerExitRuleEvent();
+ _prevctx = _localctx;
+ {
+ setState(130);
+ switch ( getInterpreter().adaptivePredict(_input,4,_ctx) ) {
+ case 1:
+ {
+ _localctx = new MathOpContext(new ExpressionContext(_parentctx, _parentState));
+ ((MathOpContext)_localctx).left = _prevctx;
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(72);
+ if (!(precpred(_ctx, 13))) throw new FailedPredicateException(this, "precpred(_ctx, 13)");
+ setState(73);
+ ((MathOpContext)_localctx).op = _input.LT(1);
+ _la = _input.LA(1);
+ if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << T__35) | (1L << T__18) | (1L << T__6))) != 0)) ) {
+ ((MathOpContext)_localctx).op = _errHandler.recoverInline(this);
+ }
+ consume();
+ setState(74); ((MathOpContext)_localctx).right = expression(14);
+ }
+ break;
+
+ case 2:
+ {
+ _localctx = new MathOpContext(new ExpressionContext(_parentctx, _parentState));
+ ((MathOpContext)_localctx).left = _prevctx;
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(75);
+ if (!(precpred(_ctx, 12))) throw new FailedPredicateException(this, "precpred(_ctx, 12)");
+ setState(76);
+ ((MathOpContext)_localctx).op = _input.LT(1);
+ _la = _input.LA(1);
+ if ( !(_la==T__33 || _la==T__13) ) {
+ ((MathOpContext)_localctx).op = _errHandler.recoverInline(this);
+ }
+ consume();
+ setState(77); ((MathOpContext)_localctx).right = expression(13);
+ }
+ break;
+
+ case 3:
+ {
+ _localctx = new BitShiftOpContext(new ExpressionContext(_parentctx, _parentState));
+ ((BitShiftOpContext)_localctx).left = _prevctx;
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(78);
+ if (!(precpred(_ctx, 11))) throw new FailedPredicateException(this, "precpred(_ctx, 11)");
+ setState(79);
+ ((BitShiftOpContext)_localctx).op = _input.LT(1);
+ _la = _input.LA(1);
+ if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << T__41) | (1L << T__25) | (1L << T__20))) != 0)) ) {
+ ((BitShiftOpContext)_localctx).op = _errHandler.recoverInline(this);
+ }
+ consume();
+ setState(80); ((BitShiftOpContext)_localctx).right = expression(12);
+ }
+ break;
+
+ case 4:
+ {
+ _localctx = new ComparisonOpContext(new ExpressionContext(_parentctx, _parentState));
+ ((ComparisonOpContext)_localctx).left = _prevctx;
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(81);
+ if (!(precpred(_ctx, 10))) throw new FailedPredicateException(this, "precpred(_ctx, 10)");
+ setState(82);
+ ((ComparisonOpContext)_localctx).op = _input.LT(1);
+ _la = _input.LA(1);
+ if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << T__30) | (1L << T__27) | (1L << T__9) | (1L << T__4))) != 0)) ) {
+ ((ComparisonOpContext)_localctx).op = _errHandler.recoverInline(this);
+ }
+ consume();
+ setState(83); ((ComparisonOpContext)_localctx).right = expression(11);
+ }
+ break;
+
+ case 5:
+ {
+ _localctx = new ComparisonOpContext(new ExpressionContext(_parentctx, _parentState));
+ ((ComparisonOpContext)_localctx).left = _prevctx;
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(84);
+ if (!(precpred(_ctx, 8))) throw new FailedPredicateException(this, "precpred(_ctx, 8)");
+ setState(85);
+ ((ComparisonOpContext)_localctx).op = _input.LT(1);
+ _la = _input.LA(1);
+ if ( !(_la==T__28 || _la==T__7) ) {
+ ((ComparisonOpContext)_localctx).op = _errHandler.recoverInline(this);
+ }
+ consume();
+ setState(86); ((ComparisonOpContext)_localctx).right = expression(9);
+ }
+ break;
+
+ case 6:
+ {
+ _localctx = new BinaryOpContext(new ExpressionContext(_parentctx, _parentState));
+ ((BinaryOpContext)_localctx).left = _prevctx;
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(87);
+ if (!(precpred(_ctx, 7))) throw new FailedPredicateException(this, "precpred(_ctx, 7)");
+ setState(88); ((BinaryOpContext)_localctx).op = match(T__38);
+ setState(89); ((BinaryOpContext)_localctx).right = expression(8);
+ }
+ break;
+
+ case 7:
+ {
+ _localctx = new BinaryOpContext(new ExpressionContext(_parentctx, _parentState));
+ ((BinaryOpContext)_localctx).left = _prevctx;
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(90);
+ if (!(precpred(_ctx, 6))) throw new FailedPredicateException(this, "precpred(_ctx, 6)");
+ setState(91); ((BinaryOpContext)_localctx).op = match(T__17);
+ setState(92); ((BinaryOpContext)_localctx).right = expression(7);
+ }
+ break;
+
+ case 8:
+ {
+ _localctx = new BinaryOpContext(new ExpressionContext(_parentctx, _parentState));
+ ((BinaryOpContext)_localctx).left = _prevctx;
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(93);
+ if (!(precpred(_ctx, 5))) throw new FailedPredicateException(this, "precpred(_ctx, 5)");
+ setState(94); ((BinaryOpContext)_localctx).op = match(T__2);
+ setState(95); ((BinaryOpContext)_localctx).right = expression(6);
+ }
+ break;
+
+ case 9:
+ {
+ _localctx = new AndOrOpContext(new ExpressionContext(_parentctx, _parentState));
+ ((AndOrOpContext)_localctx).left = _prevctx;
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(96);
+ if (!(precpred(_ctx, 4))) throw new FailedPredicateException(this, "precpred(_ctx, 4)");
+ setState(97); ((AndOrOpContext)_localctx).op = match(T__11);
+ setState(98); ((AndOrOpContext)_localctx).right = expression(5);
+ }
+ break;
+
+ case 10:
+ {
+ _localctx = new AndOrOpContext(new ExpressionContext(_parentctx, _parentState));
+ ((AndOrOpContext)_localctx).left = _prevctx;
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(99);
+ if (!(precpred(_ctx, 3))) throw new FailedPredicateException(this, "precpred(_ctx, 3)");
+ setState(100); ((AndOrOpContext)_localctx).op = match(T__10);
+ setState(101); ((AndOrOpContext)_localctx).right = expression(4);
+ }
+ break;
+
+ case 11:
+ {
+ _localctx = new TernaryOpContext(new ExpressionContext(_parentctx, _parentState));
+ ((TernaryOpContext)_localctx).left = _prevctx;
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(102);
+ if (!(precpred(_ctx, 2))) throw new FailedPredicateException(this, "precpred(_ctx, 2)");
+ setState(103); ((TernaryOpContext)_localctx).op = match(T__26);
+ setState(104); ((TernaryOpContext)_localctx).iftrue = expression(0);
+ setState(105); match(T__31);
+ setState(106); ((TernaryOpContext)_localctx).iffalse = expression(3);
+ }
+ break;
+
+ case 12:
+ {
+ _localctx = new QuestionQuestionOpContext(new ExpressionContext(_parentctx, _parentState));
+ ((QuestionQuestionOpContext)_localctx).left = _prevctx;
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(108);
+ if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)");
+ setState(109); ((QuestionQuestionOpContext)_localctx).op = match(T__8);
+ setState(110); ((QuestionQuestionOpContext)_localctx).right = expression(2);
+ }
+ break;
+
+ case 13:
+ {
+ _localctx = new DotOpContext(new ExpressionContext(_parentctx, _parentState));
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(111);
+ if (!(precpred(_ctx, 19))) throw new FailedPredicateException(this, "precpred(_ctx, 19)");
+ setState(112); match(T__14);
+ setState(113); match(Identifier);
+ }
+ break;
+
+ case 14:
+ {
+ _localctx = new BracketOpContext(new ExpressionContext(_parentctx, _parentState));
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(114);
+ if (!(precpred(_ctx, 18))) throw new FailedPredicateException(this, "precpred(_ctx, 18)");
+ setState(115); match(T__34);
+ setState(116); expression(0);
+ setState(117); match(T__40);
+ }
+ break;
+
+ case 15:
+ {
+ _localctx = new MethodInvocationContext(new ExpressionContext(_parentctx, _parentState));
+ ((MethodInvocationContext)_localctx).target = _prevctx;
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(119);
+ if (!(precpred(_ctx, 17))) throw new FailedPredicateException(this, "precpred(_ctx, 17)");
+ setState(120); match(T__14);
+ setState(121); ((MethodInvocationContext)_localctx).methodName = match(Identifier);
+ setState(122); match(T__32);
+ setState(124);
+ _la = _input.LA(1);
+ if ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << T__42) | (1L << T__39) | (1L << T__33) | (1L << T__32) | (1L << T__29) | (1L << T__24) | (1L << T__23) | (1L << T__22) | (1L << T__21) | (1L << T__19) | (1L << T__16) | (1L << T__13) | (1L << T__5) | (1L << T__0) | (1L << IntegerLiteral) | (1L << FloatingPointLiteral) | (1L << BooleanLiteral) | (1L << CharacterLiteral) | (1L << SingleQuoteString) | (1L << DoubleQuoteString) | (1L << NullLiteral) | (1L << Identifier) | (1L << ResourceReference))) != 0)) {
+ {
+ setState(123); ((MethodInvocationContext)_localctx).args = expressionList();
+ }
+ }
+
+ setState(126); match(T__15);
+ }
+ break;
+
+ case 16:
+ {
+ _localctx = new InstanceOfOpContext(new ExpressionContext(_parentctx, _parentState));
+ pushNewRecursionContext(_localctx, _startState, RULE_expression);
+ setState(127);
+ if (!(precpred(_ctx, 9))) throw new FailedPredicateException(this, "precpred(_ctx, 9)");
+ setState(128); match(T__1);
+ setState(129); type();
+ }
+ break;
+ }
+ }
+ }
+ setState(134);
+ _errHandler.sync(this);
+ _alt = getInterpreter().adaptivePredict(_input,5,_ctx);
+ }
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ unrollRecursionContexts(_parentctx);
+ }
+ return _localctx;
+ }
+
+ public static class ClassExtractionContext extends ParserRuleContext {
+ public TypeContext type() {
+ return getRuleContext(TypeContext.class,0);
+ }
+ public ClassExtractionContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_classExtraction; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterClassExtraction(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitClassExtraction(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitClassExtraction(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final ClassExtractionContext classExtraction() throws RecognitionException {
+ ClassExtractionContext _localctx = new ClassExtractionContext(_ctx, getState());
+ enterRule(_localctx, 8, RULE_classExtraction);
+ try {
+ setState(142);
+ switch (_input.LA(1)) {
+ case T__42:
+ case T__39:
+ case T__29:
+ case T__23:
+ case T__22:
+ case T__21:
+ case T__19:
+ case T__16:
+ case Identifier:
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(135); type();
+ setState(136); match(T__14);
+ setState(137); match(T__3);
+ }
+ break;
+ case T__24:
+ enterOuterAlt(_localctx, 2);
+ {
+ setState(139); match(T__24);
+ setState(140); match(T__14);
+ setState(141); match(T__3);
+ }
+ break;
+ default:
+ throw new NoViableAltException(this);
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class ExpressionListContext extends ParserRuleContext {
+ public ExpressionContext expression(int i) {
+ return getRuleContext(ExpressionContext.class,i);
+ }
+ public List<? extends ExpressionContext> expression() {
+ return getRuleContexts(ExpressionContext.class);
+ }
+ public ExpressionListContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_expressionList; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterExpressionList(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitExpressionList(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitExpressionList(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final ExpressionListContext expressionList() throws RecognitionException {
+ ExpressionListContext _localctx = new ExpressionListContext(_ctx, getState());
+ enterRule(_localctx, 10, RULE_expressionList);
+ int _la;
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(144); expression(0);
+ setState(149);
+ _errHandler.sync(this);
+ _la = _input.LA(1);
+ while (_la==T__36) {
+ {
+ {
+ setState(145); match(T__36);
+ setState(146); expression(0);
+ }
+ }
+ setState(151);
+ _errHandler.sync(this);
+ _la = _input.LA(1);
+ }
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class LiteralContext extends ParserRuleContext {
+ public StringLiteralContext stringLiteral() {
+ return getRuleContext(StringLiteralContext.class,0);
+ }
+ public JavaLiteralContext javaLiteral() {
+ return getRuleContext(JavaLiteralContext.class,0);
+ }
+ public LiteralContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_literal; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterLiteral(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitLiteral(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitLiteral(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final LiteralContext literal() throws RecognitionException {
+ LiteralContext _localctx = new LiteralContext(_ctx, getState());
+ enterRule(_localctx, 12, RULE_literal);
+ try {
+ setState(154);
+ switch (_input.LA(1)) {
+ case IntegerLiteral:
+ case FloatingPointLiteral:
+ case BooleanLiteral:
+ case CharacterLiteral:
+ case NullLiteral:
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(152); javaLiteral();
+ }
+ break;
+ case SingleQuoteString:
+ case DoubleQuoteString:
+ enterOuterAlt(_localctx, 2);
+ {
+ setState(153); stringLiteral();
+ }
+ break;
+ default:
+ throw new NoViableAltException(this);
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class IdentifierContext extends ParserRuleContext {
+ public TerminalNode Identifier() { return getToken(BindingExpressionParser.Identifier, 0); }
+ public IdentifierContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_identifier; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterIdentifier(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitIdentifier(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitIdentifier(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final IdentifierContext identifier() throws RecognitionException {
+ IdentifierContext _localctx = new IdentifierContext(_ctx, getState());
+ enterRule(_localctx, 14, RULE_identifier);
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(156); match(Identifier);
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class JavaLiteralContext extends ParserRuleContext {
+ public TerminalNode NullLiteral() { return getToken(BindingExpressionParser.NullLiteral, 0); }
+ public TerminalNode CharacterLiteral() { return getToken(BindingExpressionParser.CharacterLiteral, 0); }
+ public TerminalNode IntegerLiteral() { return getToken(BindingExpressionParser.IntegerLiteral, 0); }
+ public TerminalNode FloatingPointLiteral() { return getToken(BindingExpressionParser.FloatingPointLiteral, 0); }
+ public TerminalNode BooleanLiteral() { return getToken(BindingExpressionParser.BooleanLiteral, 0); }
+ public JavaLiteralContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_javaLiteral; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterJavaLiteral(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitJavaLiteral(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitJavaLiteral(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final JavaLiteralContext javaLiteral() throws RecognitionException {
+ JavaLiteralContext _localctx = new JavaLiteralContext(_ctx, getState());
+ enterRule(_localctx, 16, RULE_javaLiteral);
+ int _la;
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(158);
+ _la = _input.LA(1);
+ if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << IntegerLiteral) | (1L << FloatingPointLiteral) | (1L << BooleanLiteral) | (1L << CharacterLiteral) | (1L << NullLiteral))) != 0)) ) {
+ _errHandler.recoverInline(this);
+ }
+ consume();
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class StringLiteralContext extends ParserRuleContext {
+ public TerminalNode SingleQuoteString() { return getToken(BindingExpressionParser.SingleQuoteString, 0); }
+ public TerminalNode DoubleQuoteString() { return getToken(BindingExpressionParser.DoubleQuoteString, 0); }
+ public StringLiteralContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_stringLiteral; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterStringLiteral(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitStringLiteral(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitStringLiteral(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final StringLiteralContext stringLiteral() throws RecognitionException {
+ StringLiteralContext _localctx = new StringLiteralContext(_ctx, getState());
+ enterRule(_localctx, 18, RULE_stringLiteral);
+ int _la;
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(160);
+ _la = _input.LA(1);
+ if ( !(_la==SingleQuoteString || _la==DoubleQuoteString) ) {
+ _errHandler.recoverInline(this);
+ }
+ consume();
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class ExplicitGenericInvocationContext extends ParserRuleContext {
+ public ExplicitGenericInvocationSuffixContext explicitGenericInvocationSuffix() {
+ return getRuleContext(ExplicitGenericInvocationSuffixContext.class,0);
+ }
+ public TypeArgumentsContext typeArguments() {
+ return getRuleContext(TypeArgumentsContext.class,0);
+ }
+ public ExplicitGenericInvocationContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_explicitGenericInvocation; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterExplicitGenericInvocation(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitExplicitGenericInvocation(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitExplicitGenericInvocation(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final ExplicitGenericInvocationContext explicitGenericInvocation() throws RecognitionException {
+ ExplicitGenericInvocationContext _localctx = new ExplicitGenericInvocationContext(_ctx, getState());
+ enterRule(_localctx, 20, RULE_explicitGenericInvocation);
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(162); typeArguments();
+ setState(163); explicitGenericInvocationSuffix();
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class TypeArgumentsContext extends ParserRuleContext {
+ public TypeContext type(int i) {
+ return getRuleContext(TypeContext.class,i);
+ }
+ public List<? extends TypeContext> type() {
+ return getRuleContexts(TypeContext.class);
+ }
+ public TypeArgumentsContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_typeArguments; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterTypeArguments(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitTypeArguments(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitTypeArguments(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final TypeArgumentsContext typeArguments() throws RecognitionException {
+ TypeArgumentsContext _localctx = new TypeArgumentsContext(_ctx, getState());
+ enterRule(_localctx, 22, RULE_typeArguments);
+ int _la;
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(165); match(T__30);
+ setState(166); type();
+ setState(171);
+ _errHandler.sync(this);
+ _la = _input.LA(1);
+ while (_la==T__36) {
+ {
+ {
+ setState(167); match(T__36);
+ setState(168); type();
+ }
+ }
+ setState(173);
+ _errHandler.sync(this);
+ _la = _input.LA(1);
+ }
+ setState(174); match(T__9);
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class TypeContext extends ParserRuleContext {
+ public PrimitiveTypeContext primitiveType() {
+ return getRuleContext(PrimitiveTypeContext.class,0);
+ }
+ public ClassOrInterfaceTypeContext classOrInterfaceType() {
+ return getRuleContext(ClassOrInterfaceTypeContext.class,0);
+ }
+ public TypeContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_type; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterType(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitType(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitType(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final TypeContext type() throws RecognitionException {
+ TypeContext _localctx = new TypeContext(_ctx, getState());
+ enterRule(_localctx, 24, RULE_type);
+ try {
+ int _alt;
+ setState(192);
+ switch (_input.LA(1)) {
+ case Identifier:
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(176); classOrInterfaceType();
+ setState(181);
+ _errHandler.sync(this);
+ _alt = getInterpreter().adaptivePredict(_input,10,_ctx);
+ while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
+ if ( _alt==1 ) {
+ {
+ {
+ setState(177); match(T__34);
+ setState(178); match(T__40);
+ }
+ }
+ }
+ setState(183);
+ _errHandler.sync(this);
+ _alt = getInterpreter().adaptivePredict(_input,10,_ctx);
+ }
+ }
+ break;
+ case T__42:
+ case T__39:
+ case T__29:
+ case T__23:
+ case T__22:
+ case T__21:
+ case T__19:
+ case T__16:
+ enterOuterAlt(_localctx, 2);
+ {
+ setState(184); primitiveType();
+ setState(189);
+ _errHandler.sync(this);
+ _alt = getInterpreter().adaptivePredict(_input,11,_ctx);
+ while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
+ if ( _alt==1 ) {
+ {
+ {
+ setState(185); match(T__34);
+ setState(186); match(T__40);
+ }
+ }
+ }
+ setState(191);
+ _errHandler.sync(this);
+ _alt = getInterpreter().adaptivePredict(_input,11,_ctx);
+ }
+ }
+ break;
+ default:
+ throw new NoViableAltException(this);
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class ExplicitGenericInvocationSuffixContext extends ParserRuleContext {
+ public TerminalNode Identifier() { return getToken(BindingExpressionParser.Identifier, 0); }
+ public ArgumentsContext arguments() {
+ return getRuleContext(ArgumentsContext.class,0);
+ }
+ public ExplicitGenericInvocationSuffixContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_explicitGenericInvocationSuffix; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterExplicitGenericInvocationSuffix(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitExplicitGenericInvocationSuffix(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitExplicitGenericInvocationSuffix(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final ExplicitGenericInvocationSuffixContext explicitGenericInvocationSuffix() throws RecognitionException {
+ ExplicitGenericInvocationSuffixContext _localctx = new ExplicitGenericInvocationSuffixContext(_ctx, getState());
+ enterRule(_localctx, 26, RULE_explicitGenericInvocationSuffix);
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(194); match(Identifier);
+ setState(195); arguments();
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class ArgumentsContext extends ParserRuleContext {
+ public ExpressionListContext expressionList() {
+ return getRuleContext(ExpressionListContext.class,0);
+ }
+ public ArgumentsContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_arguments; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterArguments(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitArguments(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitArguments(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final ArgumentsContext arguments() throws RecognitionException {
+ ArgumentsContext _localctx = new ArgumentsContext(_ctx, getState());
+ enterRule(_localctx, 28, RULE_arguments);
+ int _la;
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(197); match(T__32);
+ setState(199);
+ _la = _input.LA(1);
+ if ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << T__42) | (1L << T__39) | (1L << T__33) | (1L << T__32) | (1L << T__29) | (1L << T__24) | (1L << T__23) | (1L << T__22) | (1L << T__21) | (1L << T__19) | (1L << T__16) | (1L << T__13) | (1L << T__5) | (1L << T__0) | (1L << IntegerLiteral) | (1L << FloatingPointLiteral) | (1L << BooleanLiteral) | (1L << CharacterLiteral) | (1L << SingleQuoteString) | (1L << DoubleQuoteString) | (1L << NullLiteral) | (1L << Identifier) | (1L << ResourceReference))) != 0)) {
+ {
+ setState(198); expressionList();
+ }
+ }
+
+ setState(201); match(T__15);
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class ClassOrInterfaceTypeContext extends ParserRuleContext {
+ public TypeArgumentsContext typeArguments(int i) {
+ return getRuleContext(TypeArgumentsContext.class,i);
+ }
+ public TerminalNode Identifier(int i) {
+ return getToken(BindingExpressionParser.Identifier, i);
+ }
+ public List<? extends TerminalNode> Identifier() { return getTokens(BindingExpressionParser.Identifier); }
+ public List<? extends TypeArgumentsContext> typeArguments() {
+ return getRuleContexts(TypeArgumentsContext.class);
+ }
+ public IdentifierContext identifier() {
+ return getRuleContext(IdentifierContext.class,0);
+ }
+ public ClassOrInterfaceTypeContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_classOrInterfaceType; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterClassOrInterfaceType(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitClassOrInterfaceType(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitClassOrInterfaceType(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final ClassOrInterfaceTypeContext classOrInterfaceType() throws RecognitionException {
+ ClassOrInterfaceTypeContext _localctx = new ClassOrInterfaceTypeContext(_ctx, getState());
+ enterRule(_localctx, 30, RULE_classOrInterfaceType);
+ try {
+ int _alt;
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(203); identifier();
+ setState(205);
+ switch ( getInterpreter().adaptivePredict(_input,14,_ctx) ) {
+ case 1:
+ {
+ setState(204); typeArguments();
+ }
+ break;
+ }
+ setState(214);
+ _errHandler.sync(this);
+ _alt = getInterpreter().adaptivePredict(_input,16,_ctx);
+ while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
+ if ( _alt==1 ) {
+ {
+ {
+ setState(207); match(T__14);
+ setState(208); match(Identifier);
+ setState(210);
+ switch ( getInterpreter().adaptivePredict(_input,15,_ctx) ) {
+ case 1:
+ {
+ setState(209); typeArguments();
+ }
+ break;
+ }
+ }
+ }
+ }
+ setState(216);
+ _errHandler.sync(this);
+ _alt = getInterpreter().adaptivePredict(_input,16,_ctx);
+ }
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class PrimitiveTypeContext extends ParserRuleContext {
+ public PrimitiveTypeContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_primitiveType; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterPrimitiveType(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitPrimitiveType(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitPrimitiveType(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final PrimitiveTypeContext primitiveType() throws RecognitionException {
+ PrimitiveTypeContext _localctx = new PrimitiveTypeContext(_ctx, getState());
+ enterRule(_localctx, 32, RULE_primitiveType);
+ int _la;
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(217);
+ _la = _input.LA(1);
+ if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << T__42) | (1L << T__39) | (1L << T__29) | (1L << T__23) | (1L << T__22) | (1L << T__21) | (1L << T__19) | (1L << T__16))) != 0)) ) {
+ _errHandler.recoverInline(this);
+ }
+ consume();
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class ResourcesContext extends ParserRuleContext {
+ public ResourceParametersContext resourceParameters() {
+ return getRuleContext(ResourceParametersContext.class,0);
+ }
+ public TerminalNode ResourceReference() { return getToken(BindingExpressionParser.ResourceReference, 0); }
+ public ResourcesContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_resources; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterResources(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitResources(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitResources(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final ResourcesContext resources() throws RecognitionException {
+ ResourcesContext _localctx = new ResourcesContext(_ctx, getState());
+ enterRule(_localctx, 34, RULE_resources);
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(219); match(ResourceReference);
+ setState(221);
+ switch ( getInterpreter().adaptivePredict(_input,17,_ctx) ) {
+ case 1:
+ {
+ setState(220); resourceParameters();
+ }
+ break;
+ }
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class ResourceParametersContext extends ParserRuleContext {
+ public ExpressionListContext expressionList() {
+ return getRuleContext(ExpressionListContext.class,0);
+ }
+ public ResourceParametersContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_resourceParameters; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).enterResourceParameters(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof BindingExpressionListener ) ((BindingExpressionListener)listener).exitResourceParameters(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof BindingExpressionVisitor<?> ) return ((BindingExpressionVisitor<? extends Result>)visitor).visitResourceParameters(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final ResourceParametersContext resourceParameters() throws RecognitionException {
+ ResourceParametersContext _localctx = new ResourceParametersContext(_ctx, getState());
+ enterRule(_localctx, 36, RULE_resourceParameters);
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(223); match(T__32);
+ setState(224); expressionList();
+ setState(225); match(T__15);
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public boolean sempred(RuleContext _localctx, int ruleIndex, int predIndex) {
+ switch (ruleIndex) {
+ case 3: return expression_sempred((ExpressionContext)_localctx, predIndex);
+ }
+ return true;
+ }
+ private boolean expression_sempred(ExpressionContext _localctx, int predIndex) {
+ switch (predIndex) {
+ case 0: return precpred(_ctx, 13);
+
+ case 1: return precpred(_ctx, 12);
+
+ case 2: return precpred(_ctx, 11);
+
+ case 3: return precpred(_ctx, 10);
+
+ case 4: return precpred(_ctx, 8);
+
+ case 5: return precpred(_ctx, 7);
+
+ case 6: return precpred(_ctx, 6);
+
+ case 7: return precpred(_ctx, 5);
+
+ case 8: return precpred(_ctx, 4);
+
+ case 9: return precpred(_ctx, 3);
+
+ case 10: return precpred(_ctx, 2);
+
+ case 11: return precpred(_ctx, 1);
+
+ case 12: return precpred(_ctx, 19);
+
+ case 13: return precpred(_ctx, 18);
+
+ case 14: return precpred(_ctx, 17);
+
+ case 15: return precpred(_ctx, 9);
+ }
+ return true;
+ }
+
+ public static final String _serializedATN =
+ "\3\uaf6f\u8320\u479d\ub75c\u4880\u1605\u191c\uab37\3:\u00e6\4\2\t\2\4"+
+ "\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t"+
+ "\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22"+
+ "\4\23\t\23\4\24\t\24\3\2\3\2\5\2+\n\2\3\3\3\3\3\3\3\3\3\3\3\4\3\4\3\4"+
+ "\5\4\65\n\4\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3"+
+ "\5\3\5\3\5\3\5\5\5I\n\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3"+
+ "\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5"+
+ "\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3"+
+ "\5\3\5\3\5\3\5\3\5\3\5\5\5\177\n\5\3\5\3\5\3\5\3\5\7\5\u0085\n\5\f\5\16"+
+ "\5\u0088\13\5\3\6\3\6\3\6\3\6\3\6\3\6\3\6\5\6\u0091\n\6\3\7\3\7\3\7\7"+
+ "\7\u0096\n\7\f\7\16\7\u0099\13\7\3\b\3\b\5\b\u009d\n\b\3\t\3\t\3\n\3\n"+
+ "\3\13\3\13\3\f\3\f\3\f\3\r\3\r\3\r\3\r\7\r\u00ac\n\r\f\r\16\r\u00af\13"+
+ "\r\3\r\3\r\3\16\3\16\3\16\7\16\u00b6\n\16\f\16\16\16\u00b9\13\16\3\16"+
+ "\3\16\3\16\7\16\u00be\n\16\f\16\16\16\u00c1\13\16\5\16\u00c3\n\16\3\17"+
+ "\3\17\3\17\3\20\3\20\5\20\u00ca\n\20\3\20\3\20\3\21\3\21\5\21\u00d0\n"+
+ "\21\3\21\3\21\3\21\5\21\u00d5\n\21\7\21\u00d7\n\21\f\21\16\21\u00da\13"+
+ "\21\3\22\3\22\3\23\3\23\5\23\u00e0\n\23\3\24\3\24\3\24\3\24\3\24\2\2\3"+
+ "\b\25\2\2\4\2\6\2\b\2\n\2\f\2\16\2\20\2\22\2\24\2\26\2\30\2\32\2\34\2"+
+ "\36\2 \2\"\2$\2&\2\2\13\4\2\f\f \4\2((--\5\2\n\n\33\33\'\'\5\2\4\4\24"+
+ "\24\31\31\6\2\17\17\22\22$$))\4\2\21\21&&\4\2/\62\65\65\3\2\63\64\b\2"+
+ "\3\3\6\6\20\20\26\30\32\32\35\35\u00f9\2(\3\2\2\2\4,\3\2\2\2\6\64\3\2"+
+ "\2\2\bH\3\2\2\2\n\u0090\3\2\2\2\f\u0092\3\2\2\2\16\u009c\3\2\2\2\20\u009e"+
+ "\3\2\2\2\22\u00a0\3\2\2\2\24\u00a2\3\2\2\2\26\u00a4\3\2\2\2\30\u00a7\3"+
+ "\2\2\2\32\u00c2\3\2\2\2\34\u00c4\3\2\2\2\36\u00c7\3\2\2\2 \u00cd\3\2\2"+
+ "\2\"\u00db\3\2\2\2$\u00dd\3\2\2\2&\u00e1\3\2\2\2(*\5\b\5\2)+\5\4\3\2*"+
+ ")\3\2\2\2*+\3\2\2\2+\3\3\2\2\2,-\7\t\2\2-.\7\b\2\2./\7!\2\2/\60\5\6\4"+
+ "\2\60\5\3\2\2\2\61\65\5\16\b\2\62\65\78\2\2\63\65\5\20\t\2\64\61\3\2\2"+
+ "\2\64\62\3\2\2\2\64\63\3\2\2\2\65\7\3\2\2\2\66\67\b\5\1\2\678\7\r\2\2"+
+ "89\5\32\16\29:\7\36\2\2:;\5\b\5\22;I\3\2\2\2<=\t\2\2\2=I\5\b\5\21>?\t"+
+ "\3\2\2?I\5\b\5\20@A\7\r\2\2AB\5\b\5\2BC\7\36\2\2CI\3\2\2\2DI\5\16\b\2"+
+ "EI\5\20\t\2FI\5\n\6\2GI\5$\23\2H\66\3\2\2\2H<\3\2\2\2H>\3\2\2\2H@\3\2"+
+ "\2\2HD\3\2\2\2HE\3\2\2\2HF\3\2\2\2HG\3\2\2\2I\u0086\3\2\2\2JK\f\17\2\2"+
+ "KL\t\4\2\2L\u0085\5\b\5\20MN\f\16\2\2NO\t\2\2\2O\u0085\5\b\5\17PQ\f\r"+
+ "\2\2QR\t\5\2\2R\u0085\5\b\5\16ST\f\f\2\2TU\t\6\2\2U\u0085\5\b\5\rVW\f"+
+ "\n\2\2WX\t\7\2\2X\u0085\5\b\5\13YZ\f\t\2\2Z[\7\7\2\2[\u0085\5\b\5\n\\"+
+ "]\f\b\2\2]^\7\34\2\2^\u0085\5\b\5\t_`\f\7\2\2`a\7+\2\2a\u0085\5\b\5\b"+
+ "bc\f\6\2\2cd\7\"\2\2d\u0085\5\b\5\7ef\f\5\2\2fg\7#\2\2g\u0085\5\b\5\6"+
+ "hi\f\4\2\2ij\7\23\2\2jk\5\b\5\2kl\7\16\2\2lm\5\b\5\5m\u0085\3\2\2\2no"+
+ "\f\3\2\2op\7%\2\2p\u0085\5\b\5\4qr\f\25\2\2rs\7\37\2\2s\u0085\7\66\2\2"+
+ "tu\f\24\2\2uv\7\13\2\2vw\5\b\5\2wx\7\5\2\2x\u0085\3\2\2\2yz\f\23\2\2z"+
+ "{\7\37\2\2{|\7\66\2\2|~\7\r\2\2}\177\5\f\7\2~}\3\2\2\2~\177\3\2\2\2\177"+
+ "\u0080\3\2\2\2\u0080\u0085\7\36\2\2\u0081\u0082\f\13\2\2\u0082\u0083\7"+
+ ",\2\2\u0083\u0085\5\32\16\2\u0084J\3\2\2\2\u0084M\3\2\2\2\u0084P\3\2\2"+
+ "\2\u0084S\3\2\2\2\u0084V\3\2\2\2\u0084Y\3\2\2\2\u0084\\\3\2\2\2\u0084"+
+ "_\3\2\2\2\u0084b\3\2\2\2\u0084e\3\2\2\2\u0084h\3\2\2\2\u0084n\3\2\2\2"+
+ "\u0084q\3\2\2\2\u0084t\3\2\2\2\u0084y\3\2\2\2\u0084\u0081\3\2\2\2\u0085"+
+ "\u0088\3\2\2\2\u0086\u0084\3\2\2\2\u0086\u0087\3\2\2\2\u0087\t\3\2\2\2"+
+ "\u0088\u0086\3\2\2\2\u0089\u008a\5\32\16\2\u008a\u008b\7\37\2\2\u008b"+
+ "\u008c\7*\2\2\u008c\u0091\3\2\2\2\u008d\u008e\7\25\2\2\u008e\u008f\7\37"+
+ "\2\2\u008f\u0091\7*\2\2\u0090\u0089\3\2\2\2\u0090\u008d\3\2\2\2\u0091"+
+ "\13\3\2\2\2\u0092\u0097\5\b\5\2\u0093\u0094\7\t\2\2\u0094\u0096\5\b\5"+
+ "\2\u0095\u0093\3\2\2\2\u0096\u0099\3\2\2\2\u0097\u0095\3\2\2\2\u0097\u0098"+
+ "\3\2\2\2\u0098\r\3\2\2\2\u0099\u0097\3\2\2\2\u009a\u009d\5\22\n\2\u009b"+
+ "\u009d\5\24\13\2\u009c\u009a\3\2\2\2\u009c\u009b\3\2\2\2\u009d\17\3\2"+
+ "\2\2\u009e\u009f\7\66\2\2\u009f\21\3\2\2\2\u00a0\u00a1\t\b\2\2\u00a1\23"+
+ "\3\2\2\2\u00a2\u00a3\t\t\2\2\u00a3\25\3\2\2\2\u00a4\u00a5\5\30\r\2\u00a5"+
+ "\u00a6\5\34\17\2\u00a6\27\3\2\2\2\u00a7\u00a8\7\17\2\2\u00a8\u00ad\5\32"+
+ "\16\2\u00a9\u00aa\7\t\2\2\u00aa\u00ac\5\32\16\2\u00ab\u00a9\3\2\2\2\u00ac"+
+ "\u00af\3\2\2\2\u00ad\u00ab\3\2\2\2\u00ad\u00ae\3\2\2\2\u00ae\u00b0\3\2"+
+ "\2\2\u00af\u00ad\3\2\2\2\u00b0\u00b1\7$\2\2\u00b1\31\3\2\2\2\u00b2\u00b7"+
+ "\5 \21\2\u00b3\u00b4\7\13\2\2\u00b4\u00b6\7\5\2\2\u00b5\u00b3\3\2\2\2"+
+ "\u00b6\u00b9\3\2\2\2\u00b7\u00b5\3\2\2\2\u00b7\u00b8\3\2\2\2\u00b8\u00c3"+
+ "\3\2\2\2\u00b9\u00b7\3\2\2\2\u00ba\u00bf\5\"\22\2\u00bb\u00bc\7\13\2\2"+
+ "\u00bc\u00be\7\5\2\2\u00bd\u00bb\3\2\2\2\u00be\u00c1\3\2\2\2\u00bf\u00bd"+
+ "\3\2\2\2\u00bf\u00c0\3\2\2\2\u00c0\u00c3\3\2\2\2\u00c1\u00bf\3\2\2\2\u00c2"+
+ "\u00b2\3\2\2\2\u00c2\u00ba\3\2\2\2\u00c3\33\3\2\2\2\u00c4\u00c5\7\66\2"+
+ "\2\u00c5\u00c6\5\36\20\2\u00c6\35\3\2\2\2\u00c7\u00c9\7\r\2\2\u00c8\u00ca"+
+ "\5\f\7\2\u00c9\u00c8\3\2\2\2\u00c9\u00ca\3\2\2\2\u00ca\u00cb\3\2\2\2\u00cb"+
+ "\u00cc\7\36\2\2\u00cc\37\3\2\2\2\u00cd\u00cf\5\20\t\2\u00ce\u00d0\5\30"+
+ "\r\2\u00cf\u00ce\3\2\2\2\u00cf\u00d0\3\2\2\2\u00d0\u00d8\3\2\2\2\u00d1"+
+ "\u00d2\7\37\2\2\u00d2\u00d4\7\66\2\2\u00d3\u00d5\5\30\r\2\u00d4\u00d3"+
+ "\3\2\2\2\u00d4\u00d5\3\2\2\2\u00d5\u00d7\3\2\2\2\u00d6\u00d1\3\2\2\2\u00d7"+
+ "\u00da\3\2\2\2\u00d8\u00d6\3\2\2\2\u00d8\u00d9\3\2\2\2\u00d9!\3\2\2\2"+
+ "\u00da\u00d8\3\2\2\2\u00db\u00dc\t\n\2\2\u00dc#\3\2\2\2\u00dd\u00df\7"+
+ "8\2\2\u00de\u00e0\5&\24\2\u00df\u00de\3\2\2\2\u00df\u00e0\3\2\2\2\u00e0"+
+ "%\3\2\2\2\u00e1\u00e2\7\r\2\2\u00e2\u00e3\5\f\7\2\u00e3\u00e4\7\36\2\2"+
+ "\u00e4\'\3\2\2\2\24*\64H~\u0084\u0086\u0090\u0097\u009c\u00ad\u00b7\u00bf"+
+ "\u00c2\u00c9\u00cf\u00d4\u00d8\u00df";
+ public static final ATN _ATN =
+ new ATNDeserializer().deserialize(_serializedATN.toCharArray());
+ static {
+ }
+}
\ No newline at end of file
diff --git a/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionVisitor.java b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionVisitor.java
new file mode 100644
index 0000000..d589a7d
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/src/main/java-gen/android/databinding/parser/BindingExpressionVisitor.java
@@ -0,0 +1,275 @@
+// Generated from BindingExpression.g4 by ANTLR 4.4
+package android.databinding.parser;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.misc.NotNull;
+import org.antlr.v4.runtime.tree.ParseTreeVisitor;
+
+/**
+ * This interface defines a complete generic visitor for a parse tree produced
+ * by {@link BindingExpressionParser}.
+ *
+ * @param <Result> The return type of the visit operation. Use {@link Void} for
+ * operations with no return type.
+ */
+public interface BindingExpressionVisitor<Result> extends ParseTreeVisitor<Result> {
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitExpression(@NotNull BindingExpressionParser.ExpressionContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#resources}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitResources(@NotNull BindingExpressionParser.ResourcesContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code BracketOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitBracketOp(@NotNull BindingExpressionParser.BracketOpContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code UnaryOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitUnaryOp(@NotNull BindingExpressionParser.UnaryOpContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code CastOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitCastOp(@NotNull BindingExpressionParser.CastOpContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#resourceParameters}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitResourceParameters(@NotNull BindingExpressionParser.ResourceParametersContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code AndOrOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitAndOrOp(@NotNull BindingExpressionParser.AndOrOpContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code MethodInvocation}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitMethodInvocation(@NotNull BindingExpressionParser.MethodInvocationContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#expressionList}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitExpressionList(@NotNull BindingExpressionParser.ExpressionListContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#classOrInterfaceType}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitClassOrInterfaceType(@NotNull BindingExpressionParser.ClassOrInterfaceTypeContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#stringLiteral}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitStringLiteral(@NotNull BindingExpressionParser.StringLiteralContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code Primary}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitPrimary(@NotNull BindingExpressionParser.PrimaryContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#type}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitType(@NotNull BindingExpressionParser.TypeContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#bindingSyntax}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitBindingSyntax(@NotNull BindingExpressionParser.BindingSyntaxContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code ComparisonOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitComparisonOp(@NotNull BindingExpressionParser.ComparisonOpContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code TernaryOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitTernaryOp(@NotNull BindingExpressionParser.TernaryOpContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#constantValue}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitConstantValue(@NotNull BindingExpressionParser.ConstantValueContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code DotOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitDotOp(@NotNull BindingExpressionParser.DotOpContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#defaults}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitDefaults(@NotNull BindingExpressionParser.DefaultsContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code BitShiftOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitBitShiftOp(@NotNull BindingExpressionParser.BitShiftOpContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code InstanceOfOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitInstanceOfOp(@NotNull BindingExpressionParser.InstanceOfOpContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code BinaryOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitBinaryOp(@NotNull BindingExpressionParser.BinaryOpContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#explicitGenericInvocation}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitExplicitGenericInvocation(@NotNull BindingExpressionParser.ExplicitGenericInvocationContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code Resource}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitResource(@NotNull BindingExpressionParser.ResourceContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#typeArguments}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitTypeArguments(@NotNull BindingExpressionParser.TypeArgumentsContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code Grouping}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitGrouping(@NotNull BindingExpressionParser.GroupingContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code MathOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitMathOp(@NotNull BindingExpressionParser.MathOpContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#classExtraction}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitClassExtraction(@NotNull BindingExpressionParser.ClassExtractionContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#arguments}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitArguments(@NotNull BindingExpressionParser.ArgumentsContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#primitiveType}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitPrimitiveType(@NotNull BindingExpressionParser.PrimitiveTypeContext ctx);
+
+ /**
+ * Visit a parse tree produced by the {@code QuestionQuestionOp}
+ * labeled alternative in {@link BindingExpressionParser#expression}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitQuestionQuestionOp(@NotNull BindingExpressionParser.QuestionQuestionOpContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#javaLiteral}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitJavaLiteral(@NotNull BindingExpressionParser.JavaLiteralContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#explicitGenericInvocationSuffix}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitExplicitGenericInvocationSuffix(@NotNull BindingExpressionParser.ExplicitGenericInvocationSuffixContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#identifier}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitIdentifier(@NotNull BindingExpressionParser.IdentifierContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link BindingExpressionParser#literal}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitLiteral(@NotNull BindingExpressionParser.LiteralContext ctx);
+}
\ No newline at end of file
diff --git a/tools/data-binding/grammarBuilder/src/main/java/com/android/databinder/parser/Main.java b/tools/data-binding/grammarBuilder/src/main/java/com/android/databinder/parser/Main.java
new file mode 100644
index 0000000..ba5c395
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/src/main/java/com/android/databinder/parser/Main.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 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.databinder.parser;
+
+import org.antlr.v4.runtime.ANTLRInputStream;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.tree.ParseTreeWalker;
+
+public class Main {
+ static String input = "`name` + last_name";
+ static class Field {
+ String fieldName;
+ }
+ public static void main(String[] args) {
+ // ANTLRInputStream inputStream = new ANTLRInputStream(input);
+// DataBinderLexer lexer = new DataBinderLexer(inputStream);
+// CommonTokenStream tokenStream = new CommonTokenStream(lexer);
+// DataBinderParser parser = new DataBinderParser(tokenStream);
+// ParseTreeWalker walker = new ParseTreeWalker();
+// System.out.println(parser.expr().toStringTree(parser));
+ float[] aa = new float[2];
+
+ }
+}
diff --git a/tools/data-binding/grammarBuilder/src/test/java/android/databinding/BindingExpressionParserTest.java b/tools/data-binding/grammarBuilder/src/test/java/android/databinding/BindingExpressionParserTest.java
new file mode 100644
index 0000000..9197ccf4
--- /dev/null
+++ b/tools/data-binding/grammarBuilder/src/test/java/android/databinding/BindingExpressionParserTest.java
@@ -0,0 +1,371 @@
+package android.databinding;
+
+import android.databinding.parser.BindingExpressionLexer;
+import android.databinding.parser.BindingExpressionParser;
+import android.databinding.parser.BindingExpressionParser.AndOrOpContext;
+import android.databinding.parser.BindingExpressionParser.BinaryOpContext;
+import android.databinding.parser.BindingExpressionParser.BindingSyntaxContext;
+import android.databinding.parser.BindingExpressionParser.BitShiftOpContext;
+import android.databinding.parser.BindingExpressionParser.ComparisonOpContext;
+import android.databinding.parser.BindingExpressionParser.DefaultsContext;
+import android.databinding.parser.BindingExpressionParser.DotOpContext;
+import android.databinding.parser.BindingExpressionParser.ExpressionContext;
+import android.databinding.parser.BindingExpressionParser.GroupingContext;
+import android.databinding.parser.BindingExpressionParser.LiteralContext;
+import android.databinding.parser.BindingExpressionParser.MathOpContext;
+import android.databinding.parser.BindingExpressionParser.PrimaryContext;
+import android.databinding.parser.BindingExpressionParser.PrimitiveTypeContext;
+import android.databinding.parser.BindingExpressionParser.QuestionQuestionOpContext;
+import android.databinding.parser.BindingExpressionParser.ResourceContext;
+import android.databinding.parser.BindingExpressionParser.StringLiteralContext;
+import android.databinding.parser.BindingExpressionParser.TernaryOpContext;
+import android.databinding.parser.BindingExpressionParser.UnaryOpContext;
+
+import org.antlr.v4.runtime.ANTLRInputStream;
+import org.antlr.v4.runtime.CommonTokenStream;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.tree.TerminalNode;
+import org.junit.Test;
+
+import java.io.StringReader;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class BindingExpressionParserTest {
+
+ @Test
+ public void testSingleQuoteStringLiteral() throws Exception {
+ String expr = "`test`";
+ LiteralContext literal = parseLiteral(expr);
+ assertNotNull(literal);
+ StringLiteralContext stringLiteral = literal.stringLiteral();
+ assertNotNull(stringLiteral);
+ TerminalNode singleQuote = stringLiteral.SingleQuoteString();
+ Token token = singleQuote.getSymbol();
+ assertEquals("`test`", token.getText());
+ }
+
+ @Test
+ public void testDoubleQuoteStringLiteral() throws Exception {
+ String expr = "\"test\"";
+
+ LiteralContext literal = parseLiteral(expr);
+ StringLiteralContext stringLiteral = literal.stringLiteral();
+ TerminalNode singleQuote = stringLiteral.DoubleQuoteString();
+ Token token = singleQuote.getSymbol();
+ assertEquals("\"test\"", token.getText());
+ }
+
+ @Test
+ public void testSingleQuoteEscapeStringLiteral() throws Exception {
+ String expr = "`\"t\\`est\"`";
+ LiteralContext literal = parseLiteral(expr);
+ StringLiteralContext stringLiteral = literal.stringLiteral();
+ TerminalNode singleQuote = stringLiteral.SingleQuoteString();
+ Token token = singleQuote.getSymbol();
+ assertEquals("`\"t\\`est\"`", token.getText());
+ }
+
+ @Test
+ public void testCharLiteral() throws Exception {
+ LiteralContext literal = parseLiteral("'c'");
+ assertEquals("'c'", literal.getText());
+ literal = parseLiteral("'\\u0054'");
+ assertEquals("'\\u0054'", literal.getText());
+ literal = parseLiteral("'\\''");
+ assertEquals("'\\''", literal.getText());
+ }
+
+ @Test
+ public void testIntLiterals() throws Exception {
+ compareIntLiteral("123");
+ compareIntLiteral("123l");
+ compareIntLiteral("1_2_3l");
+ compareIntLiteral("123L");
+ compareIntLiteral("0xdeadbeef");
+ compareIntLiteral("0xdeadbeefl");
+ compareIntLiteral("0Xdeadbeef");
+ compareIntLiteral("0xdead_beefl");
+ compareIntLiteral("0xdead_beefL");
+ compareIntLiteral("01234567");
+ compareIntLiteral("01234567L");
+ compareIntLiteral("01234567l");
+ compareIntLiteral("0123_45_67l");
+ compareIntLiteral("0b0101");
+ compareIntLiteral("0b0101_0101");
+ compareIntLiteral("0B0101_0101");
+ compareIntLiteral("0B0101_0101L");
+ compareIntLiteral("0B0101_0101l");
+ }
+
+ @Test
+ public void testFloatLiterals() throws Exception {
+ compareFloatLiteral("0.12345");
+ compareFloatLiteral("0.12345f");
+ compareFloatLiteral("0.12345F");
+ compareFloatLiteral("132450.12345F");
+ compareFloatLiteral("132450.12345");
+ compareFloatLiteral("132450e123");
+ compareFloatLiteral("132450.4e123");
+ }
+
+ @Test
+ public void testBoolLiterals() throws Exception {
+ compareBoolLiteral("true");
+ compareBoolLiteral("false");
+ }
+
+ @Test
+ public void testNullLiteral() throws Exception {
+ LiteralContext literal = parseLiteral("null");
+ String token = literal.getText();
+ assertEquals("null", token);
+ }
+
+ @Test
+ public void testVoidExtraction() throws Exception {
+ PrimaryContext primary = parsePrimary("void.class");
+ assertNotNull(primary.classExtraction());
+ assertNull(primary.classExtraction().type());
+ assertEquals("void", primary.classExtraction().getChild(0).getText());
+ }
+
+ @Test
+ public void testPrimitiveClassExtraction() throws Exception {
+ PrimaryContext primary = parsePrimary("int.class");
+ PrimitiveTypeContext type = primary.classExtraction().type().primitiveType();
+ assertEquals("int", type.getText());
+ }
+
+ @Test
+ public void testIdentifier() throws Exception {
+ PrimaryContext primary = parsePrimary("abcdEfg");
+ assertEquals("abcdEfg", primary.identifier().getText());
+ }
+
+ @Test
+ public void testUnaryOperators() throws Exception {
+ compareUnaryOperators("+");
+ compareUnaryOperators("-");
+ compareUnaryOperators("!");
+ compareUnaryOperators("~");
+ }
+
+ @Test
+ public void testMathOperators() throws Exception {
+ compareMathOperators("+");
+ compareMathOperators("-");
+ compareMathOperators("*");
+ compareMathOperators("/");
+ compareMathOperators("%");
+ }
+
+ @Test
+ public void testBitShiftOperators() throws Exception {
+ compareBitShiftOperators(">>>");
+ compareBitShiftOperators("<<");
+ compareBitShiftOperators(">>");
+ }
+
+ @Test
+ public void testComparisonShiftOperators() throws Exception {
+ compareComparisonOperators("<");
+ compareComparisonOperators(">");
+ compareComparisonOperators("<=");
+ compareComparisonOperators(">=");
+ compareComparisonOperators("==");
+ compareComparisonOperators("!=");
+ }
+
+ @Test
+ public void testAndOrOperators() throws Exception {
+ compareAndOrOperators("&&");
+ compareAndOrOperators("||");
+ }
+
+ @Test
+ public void testBinaryOperators() throws Exception {
+ compareBinaryOperators("&");
+ compareBinaryOperators("|");
+ compareBinaryOperators("^");
+ }
+
+ @Test
+ public void testTernaryOperator() throws Exception {
+ TernaryOpContext expression = parseExpression("true ? 1 : 0");
+ assertEquals(5, expression.getChildCount());
+ assertEquals("true",
+ ((PrimaryContext) expression.left).literal().javaLiteral().getText());
+ assertEquals("?", expression.op.getText());
+ assertEquals("1",
+ ((PrimaryContext) expression.iftrue).literal().javaLiteral().getText());
+ assertEquals(":", expression.getChild(3).getText());
+ assertEquals("0", ((PrimaryContext) expression.iffalse).literal().javaLiteral().getText());
+ }
+
+ @Test
+ public void testDot() throws Exception {
+ DotOpContext expression = parseExpression("one.two.three");
+ assertEquals(3, expression.getChildCount());
+ assertEquals("three", expression.Identifier().getText());
+ assertEquals(".", expression.getChild(1).getText());
+ DotOpContext left = (DotOpContext) expression.expression();
+ assertEquals("two", left.Identifier().getText());
+ assertEquals(".", left.getChild(1).getText());
+ assertEquals("one", ((PrimaryContext) left.expression()).identifier().getText());
+ }
+
+ @Test
+ public void testQuestionQuestion() throws Exception {
+ QuestionQuestionOpContext expression = parseExpression("one ?? two");
+ assertEquals(3, expression.getChildCount());
+ assertEquals("one", ((PrimaryContext) expression.left).identifier().getText());
+ assertEquals("two", ((PrimaryContext) expression.right).identifier().getText());
+ assertEquals("??", expression.op.getText());
+ }
+
+ @Test
+ public void testResourceReference() throws Exception {
+ compareResource("@id/foo_bar");
+ compareResource("@transition/foo_bar");
+ compareResource("@anim/foo_bar");
+ compareResource("@animator/foo_bar");
+ compareResource("@android:id/foo_bar");
+ compareResource("@app:id/foo_bar");
+ }
+
+ @Test
+ public void testDefaults() throws Exception {
+ BindingSyntaxContext syntax = parseExpressionString("foo.bar, default = @id/foo_bar");
+ DefaultsContext defaults = syntax.defaults();
+ assertEquals("@id/foo_bar", defaults.constantValue().ResourceReference().getText());
+ }
+
+ @Test
+ public void testParentheses() throws Exception {
+ GroupingContext grouping = parseExpression("(1234)");
+ assertEquals("1234", grouping.expression().getText());
+ }
+
+ // ---------------------- Helpers --------------------
+
+ private void compareResource(String value) throws Exception {
+ ResourceContext resourceContext = parseExpression(value);
+ assertEquals(value, resourceContext.getText());
+ }
+
+ private void compareUnaryOperators(String op) throws Exception {
+ UnaryOpContext expression = parseExpression(op + " 2");
+ assertEquals(2, expression.getChildCount());
+ assertEquals(op, expression.op.getText());
+ assertEquals("2",
+ ((PrimaryContext) expression.expression()).literal().javaLiteral()
+ .getText());
+ }
+
+ private void compareBinaryOperators(String op) throws Exception {
+ BinaryOpContext expression = parseExpression("1 " + op + " 2");
+ assertEquals(3, expression.getChildCount());
+ assertTrue(expression.left instanceof ExpressionContext);
+ String one = ((PrimaryContext) expression.left).literal().javaLiteral().getText();
+ assertEquals("1", one);
+ assertEquals(op, expression.op.getText());
+ assertTrue(expression.right instanceof ExpressionContext);
+ String two = ((PrimaryContext) expression.right).literal().javaLiteral().getText();
+ assertEquals("2", two);
+ }
+
+ private void compareMathOperators(String op) throws Exception {
+ MathOpContext expression = parseExpression("1 " + op + " 2");
+ assertEquals(3, expression.getChildCount());
+ assertTrue(expression.left instanceof ExpressionContext);
+ String one = ((PrimaryContext) expression.left).literal().javaLiteral().getText();
+ assertEquals("1", one);
+ assertEquals(op, expression.op.getText());
+ assertTrue(expression.right instanceof ExpressionContext);
+ String two = ((PrimaryContext) expression.right).literal().javaLiteral().getText();
+ assertEquals("2", two);
+ }
+
+ private void compareBitShiftOperators(String op) throws Exception {
+ BitShiftOpContext expression = parseExpression("1 " + op + " 2");
+ assertEquals(3, expression.getChildCount());
+ assertTrue(expression.left instanceof ExpressionContext);
+ String one = ((PrimaryContext) expression.left).literal().javaLiteral().getText();
+ assertEquals("1", one);
+ assertEquals(op, expression.op.getText());
+ assertTrue(expression.right instanceof ExpressionContext);
+ String two = ((PrimaryContext) expression.right).literal().javaLiteral().getText();
+ assertEquals("2", two);
+ }
+
+ private void compareComparisonOperators(String op) throws Exception {
+ ComparisonOpContext expression = parseExpression("1 " + op + " 2");
+ assertEquals(3, expression.getChildCount());
+ assertTrue(expression.left instanceof ExpressionContext);
+ String one = ((PrimaryContext) expression.left).literal().javaLiteral().getText();
+ assertEquals("1", one);
+ assertEquals(op, expression.op.getText());
+ assertTrue(expression.right instanceof ExpressionContext);
+ String two = ((PrimaryContext) expression.right).literal().javaLiteral().getText();
+ assertEquals("2", two);
+ }
+
+ private void compareAndOrOperators(String op) throws Exception {
+ AndOrOpContext expression = parseExpression("1 " + op + " 2");
+ assertEquals(3, expression.getChildCount());
+ assertTrue(expression.left instanceof ExpressionContext);
+ String one = ((PrimaryContext) expression.left).literal().javaLiteral().getText();
+ assertEquals("1", one);
+ assertEquals(op, expression.op.getText());
+ assertTrue(expression.right instanceof ExpressionContext);
+ String two = ((PrimaryContext) expression.right).literal().javaLiteral().getText();
+ assertEquals("2", two);
+ }
+
+ private void compareIntLiteral(String constant) throws Exception {
+ LiteralContext literal = parseLiteral(constant);
+ String token = literal.javaLiteral().getText();
+ assertEquals(constant, token);
+ }
+
+ private void compareFloatLiteral(String constant) throws Exception {
+ LiteralContext literal = parseLiteral(constant);
+ String token = literal.javaLiteral().getText();
+ assertEquals(constant, token);
+ }
+
+ private void compareBoolLiteral(String constant) throws Exception {
+ LiteralContext literal = parseLiteral(constant);
+ String token = literal.javaLiteral().getText();
+ assertEquals(constant, token);
+ }
+
+ private BindingSyntaxContext parse(String value) throws Exception {
+ return parseExpressionString(value);
+ }
+
+ private <T extends ExpressionContext> T parseExpression(String value) throws Exception {
+ ExpressionContext expressionContext = parse(value).expression();
+ return (T) expressionContext;
+ }
+
+ private PrimaryContext parsePrimary(String value) throws Exception {
+ return parseExpression(value);
+ }
+
+ private LiteralContext parseLiteral(String value) throws Exception {
+ return parsePrimary(value).literal();
+ }
+
+ BindingExpressionParser.BindingSyntaxContext parseExpressionString(String s) throws Exception {
+ ANTLRInputStream input = new ANTLRInputStream(new StringReader(s));
+ BindingExpressionLexer lexer = new BindingExpressionLexer(input);
+ CommonTokenStream tokens = new CommonTokenStream(lexer);
+ BindingExpressionParser parser = new BindingExpressionParser(tokens);
+ return parser.bindingSyntax();
+ }
+}
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/app/.gitignore b/tools/data-binding/integration-tests/IndependentLibrary/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/app/build.gradle b/tools/data-binding/integration-tests/IndependentLibrary/app/build.gradle
new file mode 100644
index 0000000..552e1a5
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/app/build.gradle
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+apply plugin: 'maven'
+apply plugin: 'com.android.library'
+apply plugin: 'com.android.databinding'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ minSdkVersion 7
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ packagingOptions {
+ exclude 'META-INF/services/javax.annotation.processing.Processor'
+ exclude 'META-INF/LICENSE.txt'
+ exclude 'META-INF/NOTICE.txt'
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile "com.android.databinding:library:${config.snapshotVersion}"
+ provided "com.android.databinding:annotationprocessor:${config.snapshotVersion}"
+}
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ repository(url: "file://${config.mavenRepoDir}")
+ pom.artifactId = 'independent-library'
+ pom.version = config.snapshotVersion
+ pom.groupId = config.testGroup
+ }
+ }
+}
+
+connectedCheck.dependsOn uploadArchives
\ No newline at end of file
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/app/proguard-rules.pro b/tools/data-binding/integration-tests/IndependentLibrary/app/proguard-rules.pro
new file mode 100644
index 0000000..b7210d1
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/yboyar/android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/app/src/androidTest/java/android/databinding/test/independentlibrary/ApplicationTest.java b/tools/data-binding/integration-tests/IndependentLibrary/app/src/androidTest/java/android/databinding/test/independentlibrary/ApplicationTest.java
new file mode 100644
index 0000000..a75974f
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/app/src/androidTest/java/android/databinding/test/independentlibrary/ApplicationTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.test.independentlibrary;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
+ */
+public class ApplicationTest extends ApplicationTestCase<Application> {
+
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/app/src/androidTest/java/android/databinding/test/independentlibrary/LibraryActivityTest.java b/tools/data-binding/integration-tests/IndependentLibrary/app/src/androidTest/java/android/databinding/test/independentlibrary/LibraryActivityTest.java
new file mode 100644
index 0000000..2672186
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/app/src/androidTest/java/android/databinding/test/independentlibrary/LibraryActivityTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.test.independentlibrary;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.view.View;
+import android.widget.TextView;
+
+public class LibraryActivityTest extends ActivityInstrumentationTestCase2<LibraryActivity> {
+ public LibraryActivityTest() {
+ super(LibraryActivity.class);
+ }
+
+ public void testTextViewContents() throws Throwable {
+ final LibraryActivity activity = getActivity();
+ assertNotNull("test sanity", activity);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ TextView textView = (TextView) activity.findViewById(R.id.fooTextView);
+ final String expected = LibraryActivity.FIELD_VALUE + " " +
+ LibraryActivity.FIELD_VALUE;
+ assertEquals(expected, textView.getText().toString());
+ }
+ });
+ }
+}
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/AndroidManifest.xml b/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..8b26ea7
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.databinding.test.independentlibrary">
+
+ <application android:allowBackup="true"
+ android:label="@string/app_name">
+ <activity android:name=".LibraryActivity"/>
+ </application>
+
+</manifest>
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/java/android/databinding/test/independentlibrary/LibraryActivity.java b/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/java/android/databinding/test/independentlibrary/LibraryActivity.java
new file mode 100644
index 0000000..18b7bec
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/java/android/databinding/test/independentlibrary/LibraryActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.test.independentlibrary;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import android.databinding.test.independentlibrary.vo.MyBindableObject;
+import android.databinding.test.independentlibrary.databinding.LibraryLayoutBinding;
+public class LibraryActivity extends Activity {
+ public static final String FIELD_VALUE = "BAR";
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LibraryLayoutBinding binding = LibraryLayoutBinding.inflate(this);
+ setContentView(binding.getRoot());
+ MyBindableObject object = new MyBindableObject();
+ object.setField(FIELD_VALUE);
+ binding.setFoo(object);
+ binding.executePendingBindings();
+ }
+}
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/java/android/databinding/test/independentlibrary/LibraryAdapter.java b/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/java/android/databinding/test/independentlibrary/LibraryAdapter.java
new file mode 100644
index 0000000..e358e62
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/java/android/databinding/test/independentlibrary/LibraryAdapter.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.test.independentlibrary;
+
+import android.databinding.BindingAdapter;
+import android.view.View;
+
+public class LibraryAdapter {
+ @BindingAdapter("myTagAttr")
+ public static void set(View view, String someTag) {
+ view.setTag(someTag);
+ }
+}
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/java/android/databinding/test/independentlibrary/vo/MyBindableObject.java b/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/java/android/databinding/test/independentlibrary/vo/MyBindableObject.java
new file mode 100644
index 0000000..da66cef
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/java/android/databinding/test/independentlibrary/vo/MyBindableObject.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.test.independentlibrary.vo;
+
+import android.databinding.BaseObservable;
+import android.databinding.test.independentlibrary.BR;
+
+import android.databinding.Bindable;
+
+public class MyBindableObject extends BaseObservable {
+ @Bindable
+ private String mField;
+
+ public String getField() {
+ return mField;
+ }
+
+ public void setField(String field) {
+ mField = field;
+ notifyPropertyChanged(BR.field);
+ }
+}
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/res/layout/library_layout.xml b/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/res/layout/library_layout.xml
new file mode 100644
index 0000000..4262eb3
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/res/layout/library_layout.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="foo" type="android.databinding.test.independentlibrary.vo.MyBindableObject"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:id="@+id/fooTextView"
+ android:text='@{foo.field + " " + foo.field}'/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/res/values/strings.xml b/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8e6caf7
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/app/src/main/res/values/strings.xml
@@ -0,0 +1,18 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <string name="app_name">IndependentLibrary</string>
+</resources>
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/build.gradle b/tools/data-binding/integration-tests/IndependentLibrary/build.gradle
new file mode 100644
index 0000000..d74b7e6
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/build.gradle
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+buildscript {
+ def Properties dataBindingProperties = new Properties()
+ dataBindingProperties.load(new FileInputStream("${projectDir}/../../databinding.properties"))
+ dataBindingProperties.mavenRepoDir = "${projectDir}/../../${dataBindingProperties.mavenRepoName}"
+ ext.config = dataBindingProperties
+ println "loaded config"
+
+ repositories {
+ jcenter()
+ maven {
+ url config.mavenRepoDir
+ }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.1.3'
+ classpath "com.android.databinding:dataBinder:${config.snapshotVersion}"
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ maven {
+ url config.mavenRepoDir
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/gradle.properties b/tools/data-binding/integration-tests/IndependentLibrary/gradle.properties
new file mode 100644
index 0000000..efd2362
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/gradle.properties
@@ -0,0 +1,33 @@
+#
+# 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.
+#
+
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/gradle/wrapper/gradle-wrapper.jar b/tools/data-binding/integration-tests/IndependentLibrary/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/gradle/wrapper/gradle-wrapper.properties b/tools/data-binding/integration-tests/IndependentLibrary/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..de86a57
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/gradlew b/tools/data-binding/integration-tests/IndependentLibrary/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/gradlew.bat b/tools/data-binding/integration-tests/IndependentLibrary/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/tools/data-binding/integration-tests/IndependentLibrary/settings.gradle b/tools/data-binding/integration-tests/IndependentLibrary/settings.gradle
new file mode 100644
index 0000000..e2afad2
--- /dev/null
+++ b/tools/data-binding/integration-tests/IndependentLibrary/settings.gradle
@@ -0,0 +1,16 @@
+/*
+ * 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.
+ */
+
+include ':app'
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/.gitignore b/tools/data-binding/integration-tests/MultiModuleTestApp/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/build.gradle b/tools/data-binding/integration-tests/MultiModuleTestApp/app/build.gradle
new file mode 100644
index 0000000..b09ed61
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/build.gradle
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'com.android.application'
+apply plugin: 'com.android.databinding'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "22"
+
+ defaultConfig {
+ applicationId "com.android.databinding.multimoduletestapp"
+ minSdkVersion 7
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ packagingOptions {
+ exclude 'META-INF/services/javax.annotation.processing.Processor'
+ exclude 'META-INF/LICENSE.txt'
+ exclude 'META-INF/NOTICE.txt'
+ }
+}
+
+println "combined ${config.testGroup}.independent-library:${config.snapshotVersion}"
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile "com.android.databinding:library:${config.snapshotVersion}"
+ compile project(':testlibrary')
+ compile "com.android.support:support-v4:+"
+ provided "com.android.databinding:annotationprocessor:${config.snapshotVersion}"
+ compile "${config.testGroup}:independent-library:${config.snapshotVersion}"
+}
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/proguard-rules.pro b/tools/data-binding/integration-tests/MultiModuleTestApp/app/proguard-rules.pro
new file mode 100644
index 0000000..b7210d1
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/yboyar/android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/androidTest/java/com/android/databinding/multimoduletestapp/ApplicationTest.java b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/androidTest/java/com/android/databinding/multimoduletestapp/ApplicationTest.java
new file mode 100644
index 0000000..b3f219c
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/androidTest/java/com/android/databinding/multimoduletestapp/ApplicationTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.multimoduletestapp;
+
+import android.databinding.testlibrary.ObservableInLibrary;
+
+import android.app.Application;
+import android.databinding.Observable;
+import android.databinding.OnPropertyChangedListener;
+import android.test.ApplicationTestCase;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
+ */
+public class ApplicationTest extends ApplicationTestCase<Application> {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/androidTest/java/com/android/databinding/multimoduletestapp/EventIdsTest.java b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/androidTest/java/com/android/databinding/multimoduletestapp/EventIdsTest.java
new file mode 100644
index 0000000..585571b
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/androidTest/java/com/android/databinding/multimoduletestapp/EventIdsTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.multimoduletestapp;
+
+import android.databinding.testlibrary.ObservableInLibrary;
+
+import android.databinding.Observable;
+import android.databinding.OnPropertyChangedListener;
+import android.os.Debug;
+import android.test.AndroidTestCase;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import android.databinding.multimoduletestapp.BR;
+
+public class EventIdsTest extends AndroidTestCase {
+ public void testLibraryObservable() {
+ ObservableInLibrary observableInLibrary = new ObservableInLibrary();
+ EventCounter ec = new EventCounter();
+ observableInLibrary.addOnPropertyChangedListener(ec);
+ ec.assertProperty(BR.libField1, 0);
+ ec.assertProperty(BR.libField2, 0);
+ ec.assertProperty(BR.sharedField, 0);
+
+ observableInLibrary.setLibField1("a");
+ ec.assertProperty(BR.libField1, 1);
+ ec.assertProperty(BR.libField2, 0);
+ ec.assertProperty(BR.sharedField, 0);
+
+ observableInLibrary.setLibField2("b");
+ ec.assertProperty(BR.libField1, 1);
+ ec.assertProperty(BR.libField2, 1);
+ ec.assertProperty(BR.sharedField, 0);
+
+ observableInLibrary.setSharedField(3);
+ ec.assertProperty(BR.libField1, 1);
+ ec.assertProperty(BR.libField2, 1);
+ ec.assertProperty(BR.sharedField, 1);
+ }
+
+ public void testAppObservable() {
+ ObservableInMainApp observableInMainApp = new ObservableInMainApp();
+ EventCounter ec = new EventCounter();
+ observableInMainApp.addOnPropertyChangedListener(ec);
+ ec.assertProperty(BR.appField1, 0);
+ ec.assertProperty(BR.appField2, 0);
+ ec.assertProperty(BR.sharedField, 0);
+
+ observableInMainApp.setAppField2(3);
+ ec.assertProperty(BR.appField1, 0);
+ ec.assertProperty(BR.appField2, 1);
+ ec.assertProperty(BR.sharedField, 0);
+
+ observableInMainApp.setAppField1("b");
+ ec.assertProperty(BR.appField1, 1);
+ ec.assertProperty(BR.appField2, 1);
+ ec.assertProperty(BR.sharedField, 0);
+
+ observableInMainApp.setSharedField(5);
+ ec.assertProperty(BR.appField1, 1);
+ ec.assertProperty(BR.appField2, 1);
+ ec.assertProperty(BR.sharedField, 1);
+ }
+
+ public void testExtendingObservable() {
+ ObservableExtendingLib observable = new ObservableExtendingLib();
+ EventCounter ec = new EventCounter();
+ observable.addOnPropertyChangedListener(ec);
+
+ ec.assertProperty(BR.childClassField, 0);
+ ec.assertProperty(BR.libField1, 0);
+ ec.assertProperty(BR.libField2, 0);
+ ec.assertProperty(BR.sharedField, 0);
+
+ observable.setChildClassField("a");
+ ec.assertProperty(BR.childClassField, 1);
+ ec.assertProperty(BR.libField1, 0);
+ ec.assertProperty(BR.libField2, 0);
+ ec.assertProperty(BR.sharedField, 0);
+
+ observable.setLibField1("b");
+ ec.assertProperty(BR.childClassField, 1);
+ ec.assertProperty(BR.libField1, 1);
+ ec.assertProperty(BR.libField2, 0);
+ ec.assertProperty(BR.sharedField, 0);
+
+ observable.setLibField2("c");
+ ec.assertProperty(BR.childClassField, 1);
+ ec.assertProperty(BR.libField1, 1);
+ ec.assertProperty(BR.libField2, 1);
+ ec.assertProperty(BR.sharedField, 0);
+
+ observable.setSharedField(2);
+ ec.assertProperty(BR.childClassField, 1);
+ ec.assertProperty(BR.libField1, 1);
+ ec.assertProperty(BR.libField2, 1);
+ ec.assertProperty(BR.sharedField, 1);
+ }
+
+ private static class EventCounter implements OnPropertyChangedListener {
+ Map<Integer, Integer> mCounter = new HashMap<>();
+
+ @Override
+ public void onPropertyChanged(Observable observable, int propertyId) {
+ mCounter.put(propertyId, get(propertyId) + 1);
+ }
+
+ public int get(int propertyId) {
+ Integer val = mCounter.get(propertyId);
+ return val == null ? 0 : val;
+ }
+
+ private void assertProperty(int propertyId, int value) {
+ assertEquals(get(propertyId), value);
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/AndroidManifest.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7e1cb9b
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.databinding.multimoduletestapp" >
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/java/android/databinding/multimoduletestapp/MainActivity.java b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/java/android/databinding/multimoduletestapp/MainActivity.java
new file mode 100644
index 0000000..d90606b
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/java/android/databinding/multimoduletestapp/MainActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.multimoduletestapp;
+
+import android.databinding.multimoduletestapp.databinding.ActivityMainBinding;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+
+public class MainActivity extends Activity {
+ ActivityMainBinding mBinder;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mBinder = ActivityMainBinding.inflate(this);
+ setContentView(mBinder.getRoot());
+ }
+
+ public ActivityMainBinding getBinder() {
+ return mBinder;
+ }
+
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/java/android/databinding/multimoduletestapp/ObservableExtendingLib.java b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/java/android/databinding/multimoduletestapp/ObservableExtendingLib.java
new file mode 100644
index 0000000..b1ef5fe
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/java/android/databinding/multimoduletestapp/ObservableExtendingLib.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.multimoduletestapp;
+
+import android.databinding.testlibrary.ObservableInLibrary;
+
+import android.databinding.Bindable;
+import android.databinding.multimoduletestapp.BR;
+
+public class ObservableExtendingLib extends ObservableInLibrary {
+ @Bindable
+ private String mChildClassField;
+
+ public String getChildClassField() {
+ return mChildClassField;
+ }
+
+ public void setChildClassField(String childClassField) {
+ mChildClassField = childClassField;
+ notifyPropertyChanged(BR.childClassField);
+ }
+}
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/java/android/databinding/multimoduletestapp/ObservableInMainApp.java b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/java/android/databinding/multimoduletestapp/ObservableInMainApp.java
new file mode 100644
index 0000000..22317c76
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/java/android/databinding/multimoduletestapp/ObservableInMainApp.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.multimoduletestapp;
+
+import android.databinding.Bindable;
+
+import android.databinding.BaseObservable;
+import android.databinding.multimoduletestapp.BR;
+
+public class ObservableInMainApp extends BaseObservable {
+ @Bindable
+ private String mAppField1;
+ @Bindable
+ private int mAppField2;
+ @Bindable
+ private int mSharedField;
+
+ public String getAppField1() {
+ return mAppField1;
+ }
+
+ public void setAppField1(String appField1) {
+ mAppField1 = appField1;
+ notifyPropertyChanged(BR.appField1);
+ }
+
+ public int getAppField2() {
+ return mAppField2;
+ }
+
+ public void setAppField2(int appField2) {
+ mAppField2 = appField2;
+ notifyPropertyChanged(BR.appField2);
+ }
+
+ public int getSharedField() {
+ return mSharedField;
+ }
+
+ public void setSharedField(int sharedField) {
+ mSharedField = sharedField;
+ notifyPropertyChanged(BR.sharedField);
+ }
+}
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/drawable-hdpi/ic_launcher.png b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/drawable-mdpi/ic_launcher.png b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/drawable-xhdpi/ic_launcher.png b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4df1894
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/activity_main.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..4549e81
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ 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.
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+ android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
+ <variable name="foo" type="String"/>
+ <TextView android:text='@{foo + " " + foo}' android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/activity_test_library_main.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/activity_test_library_main.xml
new file mode 100644
index 0000000..5a34049
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/activity_test_library_main.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ 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.
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+ android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ >
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/another_layout.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/another_layout.xml
new file mode 100644
index 0000000..891e70f
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/another_layout.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="userName" type="String"/>
+ <variable name="userLastName" type="String"/>
+ <TextView
+ android:text='@{userName + " " + userLastName}'
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/demo_layout.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/demo_layout.xml
new file mode 100644
index 0000000..54c26dd
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/demo_layout.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="name" type="String"/>
+ <variable name="lastName" type="String"/>
+ <variable name="anotherVariable" type="String"/>
+ <TextView
+ android:text='@{name + " " + lastName}'
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/some_new_layout.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/some_new_layout.xml
new file mode 100644
index 0000000..36872fe
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/layout/some_new_layout.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="name" type="String"/>
+ <variable name="lastName" type="String"/>
+ <TextView
+ android:text='@{name + " " + lastName}'
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/menu/menu_main.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..4674d4d
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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.
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
+ <item android:id="@+id/action_settings" android:title="@string/action_settings"
+ android:orderInCategory="100" android:showAsAction="never" />
+</menu>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values-v21/styles.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..9b24d4f
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <style name="AppTheme" parent="android:Theme.Material.Light">
+ </style>
+</resources>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values-w820dp/dimens.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..4719591
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values/dimens.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..c06ae3f
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values/strings.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..20d68aa
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources>
+
+ <string name="app_name">Multi Module Test App</string>
+ <string name="hello_world">Hello world!</string>
+ <string name="action_settings">Settings</string>
+
+</resources>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values/styles.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..7dc23c8
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/app/src/main/res/values/styles.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ </style>
+
+</resources>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/build.gradle b/tools/data-binding/integration-tests/MultiModuleTestApp/build.gradle
new file mode 100644
index 0000000..b9340e5
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/build.gradle
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+buildscript {
+ def Properties dataBindingProperties = new Properties()
+ dataBindingProperties.load(new FileInputStream("${projectDir}/../../databinding.properties"))
+ dataBindingProperties.mavenRepoDir = "${projectDir}/../../${dataBindingProperties.mavenRepoName}"
+ ext.config = dataBindingProperties
+ println "loaded config"
+
+ repositories {
+ jcenter()
+ maven {
+ url config.mavenRepoDir
+ }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.1.3'
+ classpath "com.android.databinding:dataBinder:${config.snapshotVersion}"
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ maven {
+ url config.mavenRepoDir
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/gradle.properties b/tools/data-binding/integration-tests/MultiModuleTestApp/gradle.properties
new file mode 100644
index 0000000..5b24ba3
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/gradle.properties
@@ -0,0 +1,34 @@
+#
+# 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.
+#
+
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/gradle/wrapper/gradle-wrapper.jar b/tools/data-binding/integration-tests/MultiModuleTestApp/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/gradle/wrapper/gradle-wrapper.properties b/tools/data-binding/integration-tests/MultiModuleTestApp/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..992e276
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/gradlew b/tools/data-binding/integration-tests/MultiModuleTestApp/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/gradlew.bat b/tools/data-binding/integration-tests/MultiModuleTestApp/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/settings.gradle b/tools/data-binding/integration-tests/MultiModuleTestApp/settings.gradle
new file mode 100644
index 0000000..c79cb3d
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/settings.gradle
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+include ':app', ':testlibrary'
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/.gitignore b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/build.gradle b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/build.gradle
new file mode 100644
index 0000000..4b2c87c
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/build.gradle
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'com.android.library'
+apply plugin: 'com.android.databinding'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.2"
+
+ defaultConfig {
+ minSdkVersion 7
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ packagingOptions {
+ exclude 'META-INF/services/javax.annotation.processing.Processor'
+ exclude 'META-INF/LICENSE.txt'
+ exclude 'META-INF/NOTICE.txt'
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile "com.android.databinding:library:${config.snapshotVersion}"
+ provided "com.android.databinding:annotationprocessor:${config.snapshotVersion}"
+}
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/proguard-rules.pro b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/proguard-rules.pro
new file mode 100644
index 0000000..b7210d1
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/yboyar/android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/androidTest/java/android/databinding/testlibrary/ApplicationTest.java b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/androidTest/java/android/databinding/testlibrary/ApplicationTest.java
new file mode 100644
index 0000000..49d1b7c
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/androidTest/java/android/databinding/testlibrary/ApplicationTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testlibrary;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
+ */
+public class ApplicationTest extends ApplicationTestCase<Application> {
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/AndroidManifest.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1f1fb2b
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.databinding.testlibrary" >
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".TestLibraryMainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/java/android/databinding/testlibrary/ObservableInLibrary.java b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/java/android/databinding/testlibrary/ObservableInLibrary.java
new file mode 100644
index 0000000..c3fa89c
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/java/android/databinding/testlibrary/ObservableInLibrary.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testlibrary;
+
+import android.databinding.Bindable;
+
+import android.databinding.testlibrary.BR;
+
+import android.databinding.BaseObservable;
+
+public class ObservableInLibrary extends BaseObservable {
+
+ @Bindable
+ private String mLibField1;
+
+ @Bindable
+ private String mLibField2;
+
+ @Bindable
+ private int mSharedField;
+
+ public String getLibField1() {
+ return mLibField1;
+ }
+
+ public void setLibField1(String libField1) {
+ mLibField1 = libField1;
+ notifyPropertyChanged(BR.libField1);
+ }
+
+ public String getLibField2() {
+ return mLibField2;
+ }
+
+ public void setLibField2(String libField2) {
+ mLibField2 = libField2;
+ notifyPropertyChanged(BR.libField2);
+ }
+
+ public int getSharedField() {
+ return mSharedField;
+ }
+
+ public void setSharedField(int sharedField) {
+ mSharedField = sharedField;
+ notifyPropertyChanged(BR.sharedField);
+ }
+}
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/java/android/databinding/testlibrary/TestLibObject.java b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/java/android/databinding/testlibrary/TestLibObject.java
new file mode 100644
index 0000000..8bf253a
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/java/android/databinding/testlibrary/TestLibObject.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testlibrary;
+
+import android.databinding.Bindable;
+
+public class TestLibObject {
+ @Bindable
+ private String mField;
+
+ public String getField() {
+ return mField;
+ }
+
+ public void setField(String field) {
+ this.mField = field;
+ }
+}
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/java/android/databinding/testlibrary/TestLibraryMainActivity.java b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/java/android/databinding/testlibrary/TestLibraryMainActivity.java
new file mode 100644
index 0000000..783d51f
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/java/android/databinding/testlibrary/TestLibraryMainActivity.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testlibrary;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.databinding.testlibrary.R;
+import android.databinding.testlibrary.databinding.ActivityTestLibraryMainBinding;
+public class TestLibraryMainActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ActivityTestLibraryMainBinding binder = ActivityTestLibraryMainBinding.inflate(this);
+ setContentView(binder.getRoot());
+
+ }
+
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_test_library_main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/drawable-hdpi/ic_launcher.png b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/drawable-mdpi/ic_launcher.png b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/drawable-xhdpi/ic_launcher.png b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/drawable-xxhdpi/ic_launcher.png b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4df1894
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/layout/activity_test_library_main.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/layout/activity_test_library_main.xml
new file mode 100644
index 0000000..d60fa98
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/layout/activity_test_library_main.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ 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.
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+ android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ tools:context=".TestLibraryMainActivity">
+ <variable name="obj1" type="android.databinding.testlibrary.TestLibObject"/>
+
+ <TextView android:text="@{obj1.field}" android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/layout/another_layout_file.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/layout/another_layout_file.xml
new file mode 100644
index 0000000..62b2c73
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/layout/another_layout_file.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ 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.
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+ android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
+ <variable name="foo" type="String"/>
+ <variable name="foo2" type="int"/>
+ <TextView android:text='@{foo + " " + foo}' android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/layout/library_only_layout.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/layout/library_only_layout.xml
new file mode 100644
index 0000000..989517d3
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/layout/library_only_layout.xml
@@ -0,0 +1,30 @@
+<!--
+ ~ 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.
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+ android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ tools:context=".TestLibraryMainActivity">
+ <variable name="obj1" type="android.databinding.testlibrary.TestLibObject"/>
+ <variable name="obj2" type="android.databinding.testlibrary.TestLibObject"/>
+
+ <TextView android:text="@{obj1.field}" android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/menu/menu_test_library_main.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/menu/menu_test_library_main.xml
new file mode 100644
index 0000000..68d936d
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/menu/menu_test_library_main.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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.
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:context=".TestLibraryMainActivity">
+ <item android:id="@+id/action_settings" android:title="@string/action_settings"
+ android:orderInCategory="100" android:showAsAction="never" />
+</menu>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/values-w820dp/dimens.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..4719591
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/values/dimens.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..c06ae3f
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/values/strings.xml b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d930ddb
--- /dev/null
+++ b/tools/data-binding/integration-tests/MultiModuleTestApp/testlibrary/src/main/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources>
+
+ <string name="app_name">Test Library</string>
+ <string name="hello_world">Hello world!</string>
+ <string name="action_settings">Settings</string>
+
+</resources>
diff --git a/tools/data-binding/integration-tests/TestApp/.gitignore b/tools/data-binding/integration-tests/TestApp/.gitignore
new file mode 100644
index 0000000..afbdab3
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/.gitignore
@@ -0,0 +1,6 @@
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
diff --git a/tools/data-binding/integration-tests/TestApp/app/.gitignore b/tools/data-binding/integration-tests/TestApp/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/tools/data-binding/integration-tests/TestApp/app/build.gradle b/tools/data-binding/integration-tests/TestApp/app/build.gradle
new file mode 100644
index 0000000..d9da119
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/build.gradle
@@ -0,0 +1,37 @@
+apply plugin: 'com.android.application'
+apply plugin: 'com.android.databinding'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "22"
+
+ defaultConfig {
+ applicationId "com.android.databinding.testapp"
+ minSdkVersion 7
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ packagingOptions {
+ exclude 'META-INF/services/javax.annotation.processing.Processor'
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile "com.android.databinding:library:${config.snapshotVersion}"
+ compile "com.android.databinding:adapters:${config.snapshotVersion}"
+ compile "com.android.support:support-v4:+"
+ provided "com.android.databinding:annotationprocessor:${config.snapshotVersion}"
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/proguard-rules.pro b/tools/data-binding/integration-tests/TestApp/app/proguard-rules.pro
new file mode 100644
index 0000000..b7210d1
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/yboyar/android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/library/DataBinderTrojan.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/library/DataBinderTrojan.java
new file mode 100644
index 0000000..763f5a4
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/library/DataBinderTrojan.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding;
+
+/**
+ * This helper is used to toggle DataBinder's package private values to change behavior for testing
+ */
+public class DataBinderTrojan {
+ public static void setBuildSdkInt(int level) {
+ ViewDataBinding.SDK_INT = level;
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/AbsListViewBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/AbsListViewBindingAdapterTest.java
new file mode 100644
index 0000000..287fa24
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/AbsListViewBindingAdapterTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.AbsListViewAdapterTestBinding;
+import android.databinding.testapp.vo.AbsListViewBindingObject;
+
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.os.Debug;
+import android.widget.ListView;
+
+public class AbsListViewBindingAdapterTest
+ extends BindingAdapterTestBase<AbsListViewAdapterTestBinding, AbsListViewBindingObject> {
+
+ ListView mView;
+
+ public AbsListViewBindingAdapterTest() {
+ super(AbsListViewAdapterTestBinding.class, AbsListViewBindingObject.class,
+ R.layout.abs_list_view_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testListSelector() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ assertEquals(mBindingObject.getListSelector().getColor(),
+ ((ColorDrawable) mView.getSelector()).getColor());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getListSelector().getColor(),
+ ((ColorDrawable) mView.getSelector()).getColor());
+ }
+ }
+
+ public void testScrollingCache() throws Throwable {
+ assertEquals(mBindingObject.isScrollingCache(), mView.isScrollingCacheEnabled());
+
+ changeValues();
+
+ assertEquals(mBindingObject.isScrollingCache(), mView.isScrollingCacheEnabled());
+ }
+
+ public void testSmoothScrollbar() throws Throwable {
+ assertEquals(mBindingObject.isSmoothScrollbar(), mView.isSmoothScrollbarEnabled());
+
+ changeValues();
+
+ assertEquals(mBindingObject.isSmoothScrollbar(), mView.isSmoothScrollbarEnabled());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/AbsSeekBarBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/AbsSeekBarBindingAdapterTest.java
new file mode 100644
index 0000000..5424309
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/AbsSeekBarBindingAdapterTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.AbsSeekBarAdapterTestBinding;
+import android.databinding.testapp.vo.AbsSeekBarBindingObject;
+
+import android.os.Build;
+import android.widget.SeekBar;
+
+public class AbsSeekBarBindingAdapterTest
+ extends BindingAdapterTestBase<AbsSeekBarAdapterTestBinding, AbsSeekBarBindingObject> {
+
+ SeekBar mView;
+
+ public AbsSeekBarBindingAdapterTest() {
+ super(AbsSeekBarAdapterTestBinding.class, AbsSeekBarBindingObject.class,
+ R.layout.abs_seek_bar_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testThumbTint() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ assertEquals(mBindingObject.getThumbTint(), mView.getThumbTintList().getDefaultColor());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getThumbTint(), mView.getThumbTintList().getDefaultColor());
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/AbsSpinnerBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/AbsSpinnerBindingAdapterTest.java
new file mode 100644
index 0000000..d1d45f5
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/AbsSpinnerBindingAdapterTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.AbsSpinnerAdapterTestBinding;
+import android.databinding.testapp.vo.AbsSpinnerBindingObject;
+
+import android.os.Build;
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
+
+public class AbsSpinnerBindingAdapterTest
+ extends BindingAdapterTestBase<AbsSpinnerAdapterTestBinding, AbsSpinnerBindingObject> {
+
+ Spinner mView;
+
+ public AbsSpinnerBindingAdapterTest() {
+ super(AbsSpinnerAdapterTestBinding.class, AbsSpinnerBindingObject.class,
+ R.layout.abs_spinner_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testEntries() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ validateEntries();
+
+ changeValues();
+
+ validateEntries();
+ }
+ }
+
+ private void validateEntries() {
+ assertEquals(mBindingObject.getEntries().length, mView.getAdapter().getCount());
+ CharSequence[] entries = mBindingObject.getEntries();
+ SpinnerAdapter adapter = mView.getAdapter();
+ for (int i = 0; i < entries.length; i++) {
+ assertEquals(adapter.getItem(i), entries[i]);
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ApplicationTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ApplicationTest.java
new file mode 100644
index 0000000..7c7a9b4
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ApplicationTest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.app.Application;
+import android.test.ApplicationTestCase;
+
+/**
+ * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
+ */
+public class ApplicationTest extends ApplicationTestCase<Application> {
+
+ public ApplicationTest() {
+ super(Application.class);
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/AutoCompleteTextViewBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/AutoCompleteTextViewBindingAdapterTest.java
new file mode 100644
index 0000000..a14d05d
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/AutoCompleteTextViewBindingAdapterTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.AutoCompleteTextViewAdapterTestBinding;
+import android.databinding.testapp.vo.AutoCompleteTextViewBindingObject;
+
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.widget.AutoCompleteTextView;
+
+public class AutoCompleteTextViewBindingAdapterTest extends
+ BindingAdapterTestBase<AutoCompleteTextViewAdapterTestBinding,
+ AutoCompleteTextViewBindingObject> {
+
+ AutoCompleteTextView mView;
+
+ public AutoCompleteTextViewBindingAdapterTest() {
+ super(AutoCompleteTextViewAdapterTestBinding.class, AutoCompleteTextViewBindingObject.class,
+ R.layout.auto_complete_text_view_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testCompletionThreshold() throws Throwable {
+ assertEquals(mBindingObject.getCompletionThreshold(), mView.getThreshold());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getCompletionThreshold(), mView.getThreshold());
+ }
+
+ public void testPopupBackground() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ assertEquals(mBindingObject.getPopupBackground(),
+ ((ColorDrawable) mView.getDropDownBackground()).getColor());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getPopupBackground(),
+ ((ColorDrawable) mView.getDropDownBackground()).getColor());
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BaseDataBinderTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BaseDataBinderTest.java
new file mode 100644
index 0000000..ff85523
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BaseDataBinderTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.ViewDataBinding;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.os.Looper;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Method;
+
+public class BaseDataBinderTest<T extends ViewDataBinding>
+ extends ActivityInstrumentationTestCase2<TestActivity> {
+ protected Class<T> mBinderClass;
+ private int mOrientation;
+ protected T mBinder;
+
+ public BaseDataBinderTest(final Class<T> binderClass) {
+ this(binderClass, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+
+ public BaseDataBinderTest(final Class<T> binderClass, final int orientation) {
+ super(TestActivity.class);
+ mBinderClass = binderClass;
+ mOrientation = orientation;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ getActivity().setRequestedOrientation(mOrientation);
+ createBinder();
+ }
+
+ public boolean isMainThread() {
+ return Looper.myLooper() == Looper.getMainLooper();
+ }
+
+ protected void createBinder() {
+ mBinder = null;
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Method method = null;
+ try {
+ method = mBinderClass.getMethod("inflate", Context.class);
+ mBinder = (T) method.invoke(null, getActivity());
+ getActivity().setContentView(mBinder.getRoot());
+ } catch (Exception e) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ e.printStackTrace(pw);
+ fail("Error creating binder: " + sw.toString());
+ }
+ }
+ });
+ if (!isMainThread()) {
+ getInstrumentation().waitForIdleSync();
+ }
+ assertNotNull(mBinder);
+ }
+
+ protected void assertMethod(Class<?> klass, String methodName) throws NoSuchMethodException {
+ assertEquals(klass, mBinder.getClass().getDeclaredMethod(methodName).getReturnType());
+ }
+
+ protected void assertField(Class<?> klass, String fieldName) throws NoSuchFieldException {
+ assertEquals(klass, mBinder.getClass().getDeclaredField(fieldName).getType());
+ }
+
+ protected void assertPublicField(Class<?> klass, String fieldName) throws NoSuchFieldException {
+ assertEquals(klass, mBinder.getClass().getField(fieldName).getType());
+ }
+
+ protected void assertNoField(String fieldName) {
+ Exception[] ex = new Exception[1];
+ try {
+ mBinder.getClass().getField(fieldName);
+ } catch (NoSuchFieldException e) {
+ ex[0] = e;
+ }
+ assertNotNull(ex[0]);
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BaseLandDataBinderTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BaseLandDataBinderTest.java
new file mode 100644
index 0000000..830e79b
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BaseLandDataBinderTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.ViewDataBinding;
+
+import android.content.pm.ActivityInfo;
+
+public class BaseLandDataBinderTest<T extends ViewDataBinding> extends BaseDataBinderTest<T> {
+
+ public BaseLandDataBinderTest(Class<T> binderClass, int layoutId) {
+ super(binderClass, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BaseObservableTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BaseObservableTest.java
new file mode 100644
index 0000000..c168ecb
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BaseObservableTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.BaseObservable;
+import android.databinding.testapp.databinding.BasicBindingBinding;
+
+import android.databinding.Observable;
+import android.databinding.OnPropertyChangedListener;
+
+import java.util.ArrayList;
+
+public class BaseObservableTest extends BaseDataBinderTest<BasicBindingBinding> {
+ private BaseObservable mObservable;
+ private ArrayList<Integer> mNotifications = new ArrayList<>();
+ private OnPropertyChangedListener mListener = new OnPropertyChangedListener() {
+ @Override
+ public void onPropertyChanged(Observable observable, int i) {
+ assertEquals(mObservable, observable);
+ mNotifications.add(i);
+ }
+ };
+
+ public BaseObservableTest() {
+ super(BasicBindingBinding.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ mNotifications.clear();
+ mObservable = new BaseObservable();
+ }
+
+ public void testAddListener() {
+ mObservable.notifyChange();
+ assertTrue(mNotifications.isEmpty());
+ mObservable.addOnPropertyChangedListener(mListener);
+ mObservable.notifyChange();
+ assertFalse(mNotifications.isEmpty());
+ }
+
+ public void testRemoveListener() {
+ // test there is no exception when the listener isn't there
+ mObservable.removeOnPropertyChangedListener(mListener);
+
+ mObservable.addOnPropertyChangedListener(mListener);
+ mObservable.notifyChange();
+ mNotifications.clear();
+ mObservable.removeOnPropertyChangedListener(mListener);
+ mObservable.notifyChange();
+ assertTrue(mNotifications.isEmpty());
+
+ // test there is no exception when the listener isn't there
+ mObservable.removeOnPropertyChangedListener(mListener);
+ }
+
+ public void testNotifyChange() {
+ mObservable.addOnPropertyChangedListener(mListener);
+ mObservable.notifyChange();
+ assertEquals(1, mNotifications.size());
+ assertEquals(0, (int) mNotifications.get(0));
+ }
+
+ public void testNotifyPropertyChanged() {
+ final int expectedId = 100;
+ mObservable.addOnPropertyChangedListener(mListener);
+ mObservable.notifyPropertyChanged(expectedId);
+ assertEquals(1, mNotifications.size());
+ assertEquals(expectedId, (int) mNotifications.get(0));
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BasicBindingTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BasicBindingTest.java
new file mode 100644
index 0000000..024d7f2
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BasicBindingTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.BasicBindingBinding;
+
+import android.test.UiThreadTest;
+
+public class BasicBindingTest extends BaseDataBinderTest<BasicBindingBinding> {
+ public BasicBindingTest() {
+ super(BasicBindingBinding.class);
+ }
+
+ @UiThreadTest
+ public void testTextViewContentInInitialization() {
+ assertAB("X", "Y");
+ }
+
+ @UiThreadTest
+ public void testNullValuesInInitialization() {
+ assertAB(null, null);
+ }
+
+ @UiThreadTest
+ public void testSecondIsNullInInitialization() {
+ assertAB(null, "y");
+ }
+
+ @UiThreadTest
+ public void testFirstIsNullInInitialization() {
+ assertAB("x", null);
+ }
+
+ @UiThreadTest
+ public void testTextViewContent() {
+ assertAB("X", "Y");
+ }
+
+ @UiThreadTest
+ public void testNullValues() {
+ assertAB(null, null);
+ }
+
+ @UiThreadTest
+ public void testSecondIsNull() {
+ assertAB(null, "y");
+ }
+
+ @UiThreadTest
+ public void testFirstIsNull() {
+ assertAB("x", null);
+ }
+
+ private void assertAB(String a, String b) {
+ mBinder.setA(a);
+ mBinder.setB(b);
+ rebindAndAssert(a + b);
+ }
+
+ private void rebindAndAssert(String text) {
+ mBinder.executePendingBindings();
+ assertEquals(text, mBinder.textView.getText().toString());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BasicDependantBindingTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BasicDependantBindingTest.java
new file mode 100644
index 0000000..536a00a
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BasicDependantBindingTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.BasicDependantBindingBinding;
+import android.databinding.testapp.vo.NotBindableVo;
+
+import android.test.UiThreadTest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BasicDependantBindingTest extends BaseDataBinderTest<BasicDependantBindingBinding> {
+
+ public BasicDependantBindingTest() {
+ super(BasicDependantBindingBinding.class);
+ }
+
+ public List<NotBindableVo> permutations(String value) {
+ List<NotBindableVo> result = new ArrayList<>();
+ result.add(null);
+ result.add(new NotBindableVo(null));
+ result.add(new NotBindableVo(value));
+ return result;
+ }
+
+ @UiThreadTest
+ public void testAllPermutations() {
+ List<NotBindableVo> obj1s = permutations("a");
+ List<NotBindableVo> obj2s = permutations("b");
+ for (NotBindableVo obj1 : obj1s) {
+ for (NotBindableVo obj2 : obj2s) {
+ createBinder(); //get a new one
+ testWith(obj1, obj2);
+ createBinder();
+ mBinder.executePendingBindings();
+ testWith(obj1, obj2);
+ }
+ }
+ }
+
+ private void testWith(NotBindableVo obj1, NotBindableVo obj2) {
+ mBinder.setObj1(obj1);
+ mBinder.setObj2(obj2);
+ mBinder.executePendingBindings();
+ assertValues(safeGet(obj1), safeGet(obj2),
+ obj1 == null ? "" : obj1.mergeStringFields(obj2),
+ obj2 == null ? "" : obj2.mergeStringFields(obj1),
+ (obj1 == null ? null : obj1.getStringValue())
+ + (obj2 == null ? null : obj2.getStringValue())
+ );
+ }
+
+ private String safeGet(NotBindableVo vo) {
+ if (vo == null || vo.getStringValue() == null) {
+ return "";
+ }
+ return vo.getStringValue();
+ }
+
+ private void assertValues(String textView1, String textView2,
+ String mergedView1, String mergedView2, String rawMerge) {
+ assertEquals(textView1, mBinder.textView1.getText().toString());
+ assertEquals(textView2, mBinder.textView2.getText().toString());
+ assertEquals(mergedView1, mBinder.mergedTextView1.getText().toString());
+ assertEquals(mergedView2, mBinder.mergedTextView2.getText().toString());
+ assertEquals(rawMerge, mBinder.rawStringMerge.getText().toString());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BindToFinalFieldTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BindToFinalFieldTest.java
new file mode 100644
index 0000000..73f3811
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BindToFinalFieldTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.BindToFinalBinding;
+import android.databinding.testapp.vo.PublicFinalTestVo;
+
+import android.test.UiThreadTest;
+import android.widget.TextView;
+
+public class BindToFinalFieldTest extends BaseDataBinderTest<BindToFinalBinding>{
+
+ public BindToFinalFieldTest() {
+ super(BindToFinalBinding.class);
+ }
+
+ @UiThreadTest
+ public void testSimple() {
+ final PublicFinalTestVo vo = new PublicFinalTestVo(R.string.app_name);
+ mBinder.setObj(vo);
+ mBinder.executePendingBindings();
+ final TextView textView = (TextView) mBinder.getRoot().findViewById(R.id.text_view);
+ assertEquals(getActivity().getResources().getString(R.string.app_name), textView.getText().toString());
+ }
+
+
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BindToFinalObservableFieldTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BindToFinalObservableFieldTest.java
new file mode 100644
index 0000000..72c2efb
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BindToFinalObservableFieldTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.BindToFinalObservableBinding;
+import android.databinding.testapp.vo.PublicFinalWithObservableTestVo;
+
+import android.test.UiThreadTest;
+import android.widget.TextView;
+
+public class BindToFinalObservableFieldTest extends BaseDataBinderTest<BindToFinalObservableBinding>{
+
+ public BindToFinalObservableFieldTest() {
+ super(BindToFinalObservableBinding.class);
+ }
+
+ @UiThreadTest
+ public void testSimple() {
+ final PublicFinalWithObservableTestVo vo = new PublicFinalWithObservableTestVo(R.string.app_name);
+ mBinder.setObj(vo);
+ mBinder.executePendingBindings();
+ final TextView textView = (TextView) mBinder.getRoot().findViewById(R.id.text_view);
+ assertEquals(getActivity().getResources().getString(R.string.app_name), textView.getText().toString());
+ vo.myFinalVo.setVal(R.string.rain);
+ mBinder.executePendingBindings();
+ assertEquals("The field should be observed and its notify event should've invalidated"
+ + " binder flags.", getActivity().getResources().getString(R.string.rain),
+ textView.getText().toString());
+ }
+
+
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BindingAdapterTestBase.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BindingAdapterTestBase.java
new file mode 100644
index 0000000..730ebb2
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BindingAdapterTestBase.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.ViewDataBinding;
+import android.databinding.testapp.vo.BindingAdapterBindingObject;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class BindingAdapterTestBase<T extends ViewDataBinding, V extends BindingAdapterBindingObject>
+ extends BaseDataBinderTest<T> {
+ private Class<V> mBindingObjectClass;
+
+ protected V mBindingObject;
+
+ private Method mSetMethod;
+
+ public BindingAdapterTestBase(Class<T> binderClass, Class<V> observableClass, int layoutId) {
+ super(binderClass);
+ mBindingObjectClass = observableClass;
+ try {
+ mSetMethod = binderClass.getDeclaredMethod("setObj", observableClass);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ try {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mBindingObject = mBindingObjectClass.newInstance();
+ mSetMethod.invoke(mBinder, mBindingObject);
+ mBinder.executePendingBindings();
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ } catch (InstantiationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ } catch (Throwable throwable) {
+ throw new Exception(throwable);
+ }
+ }
+
+ protected void changeValues() throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mBindingObject.changeValues();
+ mBinder.executePendingBindings();
+ }
+ });
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BracketTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BracketTest.java
new file mode 100644
index 0000000..20d90ed
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/BracketTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.BracketTestBinding;
+
+import android.test.UiThreadTest;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.util.SparseLongArray;
+
+public class BracketTest extends BaseDataBinderTest<BracketTestBinding> {
+ private String[] mArray = {
+ "Hello World"
+ };
+
+ private SparseArray<String> mSparseArray = new SparseArray<>();
+ private SparseIntArray mSparseIntArray = new SparseIntArray();
+ private SparseBooleanArray mSparseBooleanArray = new SparseBooleanArray();
+ private SparseLongArray mSparseLongArray = new SparseLongArray();
+ private LongSparseArray<String> mLongSparseArray = new LongSparseArray<>();
+
+ public BracketTest() {
+ super(BracketTestBinding.class);
+ mSparseArray.put(0, "Hello");
+ mLongSparseArray.put(0, "World");
+ mSparseIntArray.put(0, 100);
+ mSparseBooleanArray.put(0, true);
+ mSparseLongArray.put(0, 5);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ try {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mBinder.setArray(mArray);
+ mBinder.setSparseArray(mSparseArray);
+ mBinder.setSparseIntArray(mSparseIntArray);
+ mBinder.setSparseBooleanArray(mSparseBooleanArray);
+ mBinder.setSparseLongArray(mSparseLongArray);
+ mBinder.setLongSparseArray(mLongSparseArray);
+
+ mBinder.executePendingBindings();
+ }
+ });
+ } catch (Throwable throwable) {
+ throw new Exception(throwable);
+ }
+ }
+
+ @UiThreadTest
+ public void testBrackets() {
+ assertEquals("Hello World", mBinder.arrayText.getText().toString());
+ assertEquals("Hello", mBinder.sparseArrayText.getText().toString());
+ assertEquals("World", mBinder.longSparseArrayText.getText().toString());
+ assertEquals("100", mBinder.sparseIntArrayText.getText().toString());
+ assertEquals("true", mBinder.sparseBooleanArrayText.getText().toString());
+ assertEquals("5", mBinder.sparseLongArrayText.getText().toString());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/CastTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/CastTest.java
new file mode 100644
index 0000000..2fce2d4
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/CastTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.CastTestBinding;
+
+import android.support.v4.util.ArrayMap;
+import android.test.UiThreadTest;
+
+import java.util.ArrayList;
+
+public class CastTest extends BaseDataBinderTest<CastTestBinding> {
+ ArrayList<String> mValues = new ArrayList<>();
+ ArrayMap<String, String> mMap = new ArrayMap<>();
+
+ public CastTest() {
+ super(CastTestBinding.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ try {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mValues.clear();
+ mValues.add("hello");
+ mValues.add("world");
+ mValues.add("not seen");
+ mMap.clear();
+ mMap.put("hello", "world");
+ mMap.put("world", "hello");
+ mBinder.setList(mValues);
+ mBinder.setMap(mMap);
+ mBinder.executePendingBindings();
+ }
+ });
+ } catch (Throwable throwable) {
+ throw new Exception(throwable);
+ }
+ }
+
+ @UiThreadTest
+ public void testCast() throws Throwable {
+ assertEquals("hello", mBinder.textView0.getText().toString());
+ assertEquals("world", mBinder.textView1.getText().toString());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/CheckedTextViewBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/CheckedTextViewBindingAdapterTest.java
new file mode 100644
index 0000000..68464e6
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/CheckedTextViewBindingAdapterTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.CheckedTextViewAdapterTestBinding;
+import android.databinding.testapp.vo.CheckedTextViewBindingObject;
+
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.widget.CheckedTextView;
+
+public class CheckedTextViewBindingAdapterTest extends
+ BindingAdapterTestBase<CheckedTextViewAdapterTestBinding, CheckedTextViewBindingObject> {
+
+ CheckedTextView mView;
+
+ public CheckedTextViewBindingAdapterTest() {
+ super(CheckedTextViewAdapterTestBinding.class, CheckedTextViewBindingObject.class,
+ R.layout.checked_text_view_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testView() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ assertEquals(mBindingObject.getCheckMark().getColor(),
+ ((ColorDrawable) mView.getCheckMarkDrawable()).getColor());
+ assertEquals(mBindingObject.getCheckMarkTint(),
+ mView.getCheckMarkTintList().getDefaultColor());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getCheckMark().getColor(),
+ ((ColorDrawable) mView.getCheckMarkDrawable()).getColor());
+ assertEquals(mBindingObject.getCheckMarkTint(),
+ mView.getCheckMarkTintList().getDefaultColor());
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/CompoundButtonBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/CompoundButtonBindingAdapterTest.java
new file mode 100644
index 0000000..7845923
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/CompoundButtonBindingAdapterTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.CompoundButtonAdapterTestBinding;
+import android.databinding.testapp.vo.CompoundButtonBindingObject;
+
+import android.widget.CompoundButton;
+
+public class CompoundButtonBindingAdapterTest extends
+ BindingAdapterTestBase<CompoundButtonAdapterTestBinding, CompoundButtonBindingObject> {
+
+ CompoundButton mView;
+
+ public CompoundButtonBindingAdapterTest() {
+ super(CompoundButtonAdapterTestBinding.class, CompoundButtonBindingObject.class,
+ R.layout.compound_button_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testCompoundButton() throws Throwable {
+ assertEquals(mBindingObject.getButtonTint(), mView.getButtonTintList().getDefaultColor());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getButtonTint(), mView.getButtonTintList().getDefaultColor());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ConditionalBindingTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ConditionalBindingTest.java
new file mode 100644
index 0000000..61839db
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ConditionalBindingTest.java
@@ -0,0 +1,34 @@
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.ConditionalBindingBinding;
+import android.databinding.testapp.vo.NotBindableVo;
+
+import android.test.UiThreadTest;
+
+public class ConditionalBindingTest extends BaseDataBinderTest<ConditionalBindingBinding>{
+
+ public ConditionalBindingTest() {
+ super(ConditionalBindingBinding.class);
+ }
+
+ @UiThreadTest
+ public void test1() {
+ testCorrectness(true, true);
+ }
+
+ private void testCorrectness(boolean cond1, boolean cond2) {
+ NotBindableVo o1 = new NotBindableVo("a");
+ NotBindableVo o2 = new NotBindableVo("b");
+ NotBindableVo o3 = new NotBindableVo("c");
+ mBinder.setObj1(o1);
+ mBinder.setObj2(o2);
+ mBinder.setObj3(o3);
+ mBinder.setCond1(cond1);
+ mBinder.setCond2(cond2);
+ mBinder.executePendingBindings();
+ final String text = mBinder.textView.getText().toString();
+ assertEquals(cond1 && cond2, "a".equals(text));
+ assertEquals(cond1 && !cond2, "b".equals(text));
+ assertEquals(!cond1, "c".equals(text));
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/FindMethodTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/FindMethodTest.java
new file mode 100644
index 0000000..ca826fa
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/FindMethodTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.FindMethodTestBinding;
+import android.databinding.testapp.vo.FindMethodBindingObject;
+
+import android.test.UiThreadTest;
+import android.widget.TextView;
+
+public class FindMethodTest
+ extends BindingAdapterTestBase<FindMethodTestBinding, FindMethodBindingObject> {
+
+ public FindMethodTest() {
+ super(FindMethodTestBinding.class, FindMethodBindingObject.class, R.layout.find_method_test);
+ }
+
+ public void testNoArg() throws Throwable {
+ TextView textView = mBinder.textView6;
+ assertEquals("no arg", textView.getText().toString());
+ }
+
+ public void testIntArg() throws Throwable {
+ TextView textView = mBinder.textView0;
+ assertEquals("1", textView.getText().toString());
+ }
+
+ public void testFloatArg() throws Throwable {
+ TextView textView = mBinder.textView1;
+ assertEquals("1.25", textView.getText().toString());
+ }
+
+ public void testStringArg() throws Throwable {
+ TextView textView = mBinder.textView2;
+ assertEquals("hello", textView.getText().toString());
+ }
+
+ public void testBoxedArg() throws Throwable {
+ TextView textView = mBinder.textView3;
+ assertEquals("1", textView.getText().toString());
+ }
+
+ public void testInheritedMethod() throws Throwable {
+ TextView textView = mBinder.textView4;
+ assertEquals("base", textView.getText().toString());
+ }
+
+ public void testInheritedMethodInt() throws Throwable {
+ TextView textView = mBinder.textView5;
+ assertEquals("base 2", textView.getText().toString());
+ }
+
+ public void testStaticMethod() throws Throwable {
+ TextView textView = mBinder.textView7;
+ assertEquals("world", textView.getText().toString());
+ }
+
+ public void testStaticField() throws Throwable {
+ TextView textView = mBinder.textView8;
+ assertEquals("hello world", textView.getText().toString());
+ }
+
+ public void testImportStaticMethod() throws Throwable {
+ TextView textView = mBinder.textView9;
+ assertEquals("world", textView.getText().toString());
+ }
+
+ public void testImportStaticField() throws Throwable {
+ TextView textView = mBinder.textView10;
+ assertEquals("hello world", textView.getText().toString());
+ }
+
+ public void testAliasStaticMethod() throws Throwable {
+ TextView textView = mBinder.textView11;
+ assertEquals("world", textView.getText().toString());
+ }
+
+ public void testAliasStaticField() throws Throwable {
+ TextView textView = mBinder.textView12;
+ assertEquals("hello world", textView.getText().toString());
+ }
+
+ @UiThreadTest
+ public void testImports() throws Throwable {
+ mBinder.setObj2(new FindMethodBindingObject.Bar<String>());
+ mBinder.executePendingBindings();
+ TextView textView = mBinder.textView15;
+ assertEquals("hello", textView.getText().toString());
+ }
+
+ @UiThreadTest
+ public void testConfusingMethods() throws Throwable {
+ assertEquals("1", mBinder.textView16.getText().toString());
+ assertEquals("1", mBinder.textView17.getText().toString());
+ assertEquals("hello", mBinder.textView18.getText().toString());
+ assertEquals("yay", mBinder.textView19.getText().toString());
+ assertEquals("hello", mBinder.textView20.getText().toString());
+ assertEquals("hello", mBinder.textView21.getText().toString());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/FrameLayoutBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/FrameLayoutBindingAdapterTest.java
new file mode 100644
index 0000000..1f2f835
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/FrameLayoutBindingAdapterTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.FrameLayoutAdapterTestBinding;
+import android.databinding.testapp.vo.FrameLayoutBindingObject;
+
+import android.os.Build;
+import android.widget.FrameLayout;
+
+public class FrameLayoutBindingAdapterTest
+ extends BindingAdapterTestBase<FrameLayoutAdapterTestBinding, FrameLayoutBindingObject> {
+
+ FrameLayout mView;
+
+ public FrameLayoutBindingAdapterTest() {
+ super(FrameLayoutAdapterTestBinding.class, FrameLayoutBindingObject.class,
+ R.layout.frame_layout_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testTint() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ assertEquals(mBindingObject.getForegroundTint(),
+ mView.getForegroundTintList().getDefaultColor());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getForegroundTint(),
+ mView.getForegroundTintList().getDefaultColor());
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ImageViewBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ImageViewBindingAdapterTest.java
new file mode 100644
index 0000000..e4dcdfe
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ImageViewBindingAdapterTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.ImageViewAdapterTestBinding;
+import android.databinding.testapp.vo.ImageViewBindingObject;
+
+import android.widget.ImageView;
+
+public class ImageViewBindingAdapterTest
+ extends BindingAdapterTestBase<ImageViewAdapterTestBinding, ImageViewBindingObject> {
+
+ ImageView mView;
+
+ public ImageViewBindingAdapterTest() {
+ super(ImageViewAdapterTestBinding.class, ImageViewBindingObject.class,
+ R.layout.image_view_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testImageView() throws Throwable {
+ assertEquals(mBindingObject.getSrc(), mView.getDrawable());
+ assertEquals(mBindingObject.getTint(), mView.getImageTintList().getDefaultColor());
+ assertEquals(mBindingObject.getTintMode(), mView.getImageTintMode());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getSrc(), mView.getDrawable());
+ assertEquals(mBindingObject.getTint(), mView.getImageTintList().getDefaultColor());
+ assertEquals(mBindingObject.getTintMode(), mView.getImageTintMode());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/IncludeTagTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/IncludeTagTest.java
new file mode 100644
index 0000000..48d6689
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/IncludeTagTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.LayoutWithIncludeBinding;
+import android.databinding.testapp.vo.NotBindableVo;
+
+import android.test.UiThreadTest;
+import android.widget.TextView;
+
+public class IncludeTagTest extends BaseDataBinderTest<LayoutWithIncludeBinding> {
+
+ public IncludeTagTest() {
+ super(LayoutWithIncludeBinding.class);
+ }
+
+ @UiThreadTest
+ public void testIncludeTag() {
+ NotBindableVo vo = new NotBindableVo(3, "a");
+ mBinder.setOuterObject(vo);
+ mBinder.executePendingBindings();
+ final TextView outerText = (TextView) mBinder.getRoot().findViewById(R.id.outerTextView);
+ assertEquals("a", outerText.getText());
+ final TextView innerText = (TextView) mBinder.getRoot().findViewById(R.id.innerTextView);
+ assertEquals("modified 3a", innerText.getText().toString());
+
+ vo.setIntValue(5);
+ vo.setStringValue("b");
+ mBinder.invalidateAll();
+ mBinder.executePendingBindings();
+ assertEquals("b", outerText.getText());
+ assertEquals("modified 5b", innerText.getText().toString());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/InnerCannotReadDependencyTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/InnerCannotReadDependencyTest.java
new file mode 100644
index 0000000..68cf06b
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/InnerCannotReadDependencyTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.InnerCannotReadDependencyBinding;
+import android.databinding.testapp.vo.BasicObject;
+import android.os.Debug;
+import android.test.UiThreadTest;
+
+import org.junit.Test;
+
+public class InnerCannotReadDependencyTest extends
+ BaseDataBinderTest<InnerCannotReadDependencyBinding> {
+
+ public InnerCannotReadDependencyTest() {
+ super(InnerCannotReadDependencyBinding.class);
+ }
+
+ @UiThreadTest
+ public void testBinding() {
+ BasicObject object = new BasicObject();
+ object.setField1("a");
+ mBinder.setObj(object);
+ mBinder.executePendingBindings();
+ assertEquals("a ", mBinder.textView.getText().toString());
+ object.setField1(null);
+ mBinder.executePendingBindings();
+ assertEquals("null ", mBinder.textView.getText().toString());
+ object.setField2("b");
+ mBinder.executePendingBindings();
+ assertEquals("null b", mBinder.textView.getText().toString());
+ object.setField1("c");
+ mBinder.executePendingBindings();
+ assertEquals("c b", mBinder.textView.getText().toString());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/LeakTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/LeakTest.java
new file mode 100644
index 0000000..0455a46
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/LeakTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.LeakTestBinding;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.widget.FrameLayout;
+
+import java.lang.ref.WeakReference;
+
+public class LeakTest extends ActivityInstrumentationTestCase2<TestActivity> {
+ WeakReference<LeakTestBinding> mWeakReference = new WeakReference<LeakTestBinding>(null);
+
+ public LeakTest() {
+ super(TestActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ try {
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ LeakTestBinding binding = LeakTestBinding.inflate(getActivity());
+ getActivity().setContentView(binding.getRoot());
+ mWeakReference = new WeakReference<LeakTestBinding>(binding);
+ binding.setName("hello world");
+ binding.executePendingBindings();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw e;
+ }
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ } catch (Throwable t) {
+ throw new Exception(t);
+ }
+ }
+
+ public void testBindingLeak() throws Throwable {
+ assertNotNull(mWeakReference.get());
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().setContentView(new FrameLayout(getActivity()));
+ }
+ });
+ WeakReference<Object> canary = new WeakReference<Object>(new Object());
+ while (canary.get() != null) {
+ byte[] b = new byte[1024 * 1024];
+ System.gc();
+ }
+ assertNull(mWeakReference.get());
+ }
+
+ // Test to ensure that when the View is detached that it doesn't rebind
+ // the dirty Views. The rebind should happen only after the root view is
+ // reattached.
+ public void testNoChangeWhenDetached() throws Throwable {
+ final LeakTestBinding binding = mWeakReference.get();
+ final AnimationWatcher watcher = new AnimationWatcher();
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().setContentView(new FrameLayout(getActivity()));
+ binding.setName("goodbye world");
+ binding.getRoot().postOnAnimation(watcher);
+ }
+ });
+
+ watcher.waitForAnimationThread();
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("hello world", binding.textView.getText().toString());
+ getActivity().setContentView(binding.getRoot());
+ binding.getRoot().postOnAnimation(watcher);
+ }
+ });
+
+ watcher.waitForAnimationThread();
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("goodbye world", binding.textView.getText().toString());
+ }
+ });
+ }
+
+ private static class AnimationWatcher implements Runnable {
+ private boolean mWaiting = true;
+
+ public void waitForAnimationThread() throws InterruptedException {
+ synchronized (this) {
+ while (mWaiting) {
+ this.wait();
+ }
+ mWaiting = true;
+ }
+ }
+
+
+ @Override
+ public void run() {
+ synchronized (this) {
+ mWaiting = false;
+ this.notifyAll();
+ }
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/LinearLayoutBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/LinearLayoutBindingAdapterTest.java
new file mode 100644
index 0000000..c601564
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/LinearLayoutBindingAdapterTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.LinearLayoutAdapterTestBinding;
+import android.databinding.testapp.vo.LinearLayoutBindingObject;
+
+import android.os.Build;
+import android.widget.LinearLayout;
+
+public class LinearLayoutBindingAdapterTest
+ extends BindingAdapterTestBase<LinearLayoutAdapterTestBinding, LinearLayoutBindingObject> {
+
+ LinearLayout mView;
+
+ public LinearLayoutBindingAdapterTest() {
+ super(LinearLayoutAdapterTestBinding.class, LinearLayoutBindingObject.class,
+ R.layout.linear_layout_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testMeasureWithLargestChild() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ assertEquals(mBindingObject.isMeasureWithLargestChild(),
+ mView.isMeasureWithLargestChildEnabled());
+
+ changeValues();
+
+ assertEquals(mBindingObject.isMeasureWithLargestChild(),
+ mView.isMeasureWithLargestChildEnabled());
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ListChangeRegistryTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ListChangeRegistryTest.java
new file mode 100644
index 0000000..365eab9
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ListChangeRegistryTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.ListChangeRegistry;
+import android.databinding.testapp.databinding.BasicBindingBinding;
+
+import android.databinding.OnListChangedListener;
+
+public class ListChangeRegistryTest extends BaseDataBinderTest<BasicBindingBinding> {
+
+ private ListChangeRegistry mListChangeRegistry;
+
+ private int mCallCount;
+
+ public ListChangeRegistryTest() {
+ super(BasicBindingBinding.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mListChangeRegistry = new ListChangeRegistry();
+ mCallCount = 0;
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mListChangeRegistry = null;
+ }
+
+ public void testNotifyChangedAll() {
+ OnListChangedListener listChangedListener = new OnListChangedListener() {
+ @Override
+ public void onChanged() {
+ mCallCount++;
+ }
+
+ @Override
+ public void onItemRangeChanged(int start, int count) {
+ fail("onItemRangeChanged should not be called");
+ }
+
+ @Override
+ public void onItemRangeInserted(int start, int count) {
+ fail("onItemRangeInserted should not be called");
+ }
+
+ @Override
+ public void onItemRangeMoved(int from, int to, int count) {
+ fail("onItemRangeMoved should not be called");
+ }
+
+ @Override
+ public void onItemRangeRemoved(int start, int count) {
+ fail("onItemRangeRemoved should not be called");
+ }
+ };
+
+ mListChangeRegistry.add(listChangedListener);
+ assertEquals(0, mCallCount);
+ mListChangeRegistry.notifyChanged(null);
+ assertEquals(1, mCallCount);
+ }
+
+ public void testNotifyChanged() {
+ final int expectedStart = 10;
+ final int expectedCount = 3;
+
+ OnListChangedListener listChangedListener = new OnListChangedListener() {
+ @Override
+ public void onChanged() {
+ fail("onChanged should not be called");
+ }
+
+ @Override
+ public void onItemRangeChanged(int start, int count) {
+ assertEquals(expectedStart, start);
+ assertEquals(expectedCount, count);
+ mCallCount++;
+ }
+
+ @Override
+ public void onItemRangeInserted(int start, int count) {
+ fail("onItemRangeInserted should not be called");
+ }
+
+ @Override
+ public void onItemRangeMoved(int from, int to, int count) {
+ fail("onItemRangeMoved should not be called");
+ }
+
+ @Override
+ public void onItemRangeRemoved(int start, int count) {
+ fail("onItemRangeRemoved should not be called");
+ }
+ };
+
+ mListChangeRegistry.add(listChangedListener);
+ assertEquals(0, mCallCount);
+ mListChangeRegistry.notifyChanged(null, expectedStart, expectedCount);
+ assertEquals(1, mCallCount);
+ }
+
+ public void testNotifyInserted() {
+ final int expectedStart = 10;
+ final int expectedCount = 3;
+
+ OnListChangedListener listChangedListener = new OnListChangedListener() {
+ @Override
+ public void onChanged() {
+ fail("onChanged should not be called");
+ }
+
+ @Override
+ public void onItemRangeChanged(int start, int count) {
+ fail("onItemRangeChanged should not be called");
+ }
+
+ @Override
+ public void onItemRangeInserted(int start, int count) {
+ assertEquals(expectedStart, start);
+ assertEquals(expectedCount, count);
+ mCallCount++;
+ }
+
+ @Override
+ public void onItemRangeMoved(int from, int to, int count) {
+ fail("onItemRangeMoved should not be called");
+ }
+
+ @Override
+ public void onItemRangeRemoved(int start, int count) {
+ fail("onItemRangeRemoved should not be called");
+ }
+ };
+
+ mListChangeRegistry.add(listChangedListener);
+ assertEquals(0, mCallCount);
+ mListChangeRegistry.notifyInserted(null, expectedStart, expectedCount);
+ assertEquals(1, mCallCount);
+ }
+
+ public void testNotifyMoved() {
+ final int expectedFrom = 10;
+ final int expectedTo = 100;
+ final int expectedCount = 3;
+
+ OnListChangedListener listChangedListener = new OnListChangedListener() {
+ @Override
+ public void onChanged() {
+ fail("onChanged should not be called");
+ }
+
+ @Override
+ public void onItemRangeChanged(int start, int count) {
+ fail("onItemRangeChanged should not be called");
+ }
+
+ @Override
+ public void onItemRangeInserted(int start, int count) {
+ fail("onItemRangeInserted should not be called");
+ }
+
+ @Override
+ public void onItemRangeMoved(int from, int to, int count) {
+ assertEquals(expectedFrom, from);
+ assertEquals(expectedTo, to);
+ assertEquals(expectedCount, count);
+ mCallCount++;
+ }
+
+ @Override
+ public void onItemRangeRemoved(int start, int count) {
+ fail("onItemRangeRemoved should not be called");
+ }
+ };
+
+ mListChangeRegistry.add(listChangedListener);
+ assertEquals(0, mCallCount);
+ mListChangeRegistry.notifyMoved(null, expectedFrom, expectedTo, expectedCount);
+ assertEquals(1, mCallCount);
+ }
+
+ public void testNotifyRemoved() {
+ final int expectedStart = 10;
+ final int expectedCount = 3;
+
+ OnListChangedListener listChangedListener = new OnListChangedListener() {
+ @Override
+ public void onChanged() {
+ fail("onChanged should not be called");
+ }
+
+ @Override
+ public void onItemRangeChanged(int start, int count) {
+ fail("onItemRangeChanged should not be called");
+ }
+
+ @Override
+ public void onItemRangeInserted(int start, int count) {
+ fail("onItemRangeInserted should not be called");
+ }
+
+ @Override
+ public void onItemRangeMoved(int from, int to, int count) {
+ fail("onItemRangeMoved should not be called");
+ }
+
+ @Override
+ public void onItemRangeRemoved(int start, int count) {
+ assertEquals(expectedStart, start);
+ assertEquals(expectedCount, count);
+ mCallCount++;
+ }
+ };
+
+ mListChangeRegistry.add(listChangedListener);
+ assertEquals(0, mCallCount);
+ mListChangeRegistry.notifyRemoved(null, expectedStart, expectedCount);
+ assertEquals(1, mCallCount);
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/MapChangeRegistryTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/MapChangeRegistryTest.java
new file mode 100644
index 0000000..fa5b536
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/MapChangeRegistryTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.MapChangeRegistry;
+import android.databinding.ObservableArrayMap;
+import android.databinding.testapp.databinding.BasicBindingBinding;
+
+import android.databinding.ObservableMap;
+import android.databinding.OnMapChangedListener;
+
+public class MapChangeRegistryTest extends BaseDataBinderTest<BasicBindingBinding> {
+
+ private int notificationCount = 0;
+
+ public MapChangeRegistryTest() {
+ super(BasicBindingBinding.class);
+ }
+
+ public void testNotifyAllChanged() {
+ MapChangeRegistry mapChangeRegistry = new MapChangeRegistry();
+
+ final ObservableMap<String, Integer> observableObj = new ObservableArrayMap<>();
+
+ final String expectedKey = "key";
+ OnMapChangedListener listener = new OnMapChangedListener<ObservableMap<String, Integer>, String>() {
+ @Override
+ public void onMapChanged(ObservableMap sender, String key) {
+ notificationCount++;
+ assertEquals(observableObj, sender);
+ assertEquals(key, expectedKey);
+ }
+ };
+ mapChangeRegistry.add(listener);
+
+ assertEquals(0, notificationCount);
+ mapChangeRegistry.notifyChange(observableObj, expectedKey);
+ assertEquals(1, notificationCount);
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/NewApiTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/NewApiTest.java
new file mode 100644
index 0000000..2c6fdf6
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/NewApiTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.DataBinderTrojan;
+import android.databinding.testapp.databinding.NewApiLayoutBinding;
+
+import android.os.Build;
+import android.test.UiThreadTest;
+import android.view.View;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class NewApiTest extends BaseDataBinderTest<NewApiLayoutBinding> {
+ public NewApiTest() {
+ super(NewApiLayoutBinding.class);
+ }
+
+ @UiThreadTest
+ public void testSetElevation() {
+ mBinder.setElevation(3);
+ mBinder.setName("foo");
+ mBinder.setChildren(new ArrayList<View>());
+ mBinder.executePendingBindings();
+ assertEquals("foo", mBinder.textView.getText().toString());
+ assertEquals(3f, mBinder.textView.getElevation());
+ }
+
+ @UiThreadTest
+ public void testSetElevationOlderAPI() {
+ DataBinderTrojan.setBuildSdkInt(1);
+ try {
+ TextView textView = mBinder.textView;
+ float originalElevation = textView.getElevation();
+ mBinder.setElevation(3);
+ mBinder.setName("foo2");
+ mBinder.executePendingBindings();
+ assertEquals("foo2", textView.getText().toString());
+ assertEquals(originalElevation, textView.getElevation());
+ } finally {
+ DataBinderTrojan.setBuildSdkInt(Build.VERSION.SDK_INT);
+ }
+ }
+
+ @UiThreadTest
+ public void testGeneric() {
+ ArrayList<View> views = new ArrayList<>();
+ mBinder.setChildren(views);
+ mBinder.executePendingBindings();
+ assertEquals(1, views.size());
+ assertSame(mBinder.textView, views.get(0));
+ }
+
+ @UiThreadTest
+ public void testGenericOlderApi() {
+ DataBinderTrojan.setBuildSdkInt(1);
+ try {
+ ArrayList<View> views = new ArrayList<>();
+ mBinder.setChildren(views);
+ mBinder.executePendingBindings();
+ // we should not call the api on older platforms.
+ assertEquals(0, views.size());
+ } finally {
+ DataBinderTrojan.setBuildSdkInt(Build.VERSION.SDK_INT);
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/NoIdTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/NoIdTest.java
new file mode 100644
index 0000000..bf58709
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/NoIdTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.NoIdTestBinding;
+
+import android.test.UiThreadTest;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+public class NoIdTest extends BaseDataBinderTest<NoIdTestBinding> {
+ public NoIdTest() {
+ super(NoIdTestBinding.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ try {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mBinder.setName("hello");
+ mBinder.setOrientation(LinearLayout.VERTICAL);
+ mBinder.executePendingBindings();
+ }
+ });
+ } catch (Throwable throwable) {
+ throw new Exception(throwable);
+ }
+ }
+
+ @UiThreadTest
+ public void testOnRoot() {
+ LinearLayout linearLayout = (LinearLayout) mBinder.getRoot();
+ assertEquals(LinearLayout.VERTICAL, linearLayout.getOrientation());
+ mBinder.setOrientation(LinearLayout.HORIZONTAL);
+ mBinder.executePendingBindings();
+ assertEquals(LinearLayout.HORIZONTAL, linearLayout.getOrientation());
+ }
+
+ @UiThreadTest
+ public void testNormal() {
+ LinearLayout linearLayout = (LinearLayout) mBinder.getRoot();
+ TextView view = (TextView) linearLayout.getChildAt(0);
+ assertEquals("hello world", view.getTag());
+ assertEquals("hello", view.getText().toString());
+ mBinder.setName("world");
+ mBinder.executePendingBindings();
+ assertEquals("world", view.getText().toString());
+ }
+
+ @UiThreadTest
+ public void testNoTag() {
+ LinearLayout linearLayout = (LinearLayout) mBinder.getRoot();
+ TextView view = (TextView) linearLayout.getChildAt(1);
+ assertNull(view.getTag());
+ }
+
+ @UiThreadTest
+ public void testResourceTag() {
+ LinearLayout linearLayout = (LinearLayout) mBinder.getRoot();
+ TextView view = (TextView) linearLayout.getChildAt(2);
+ String expectedValue = view.getResources().getString(R.string.app_name);
+ assertEquals(expectedValue, view.getTag());
+ }
+
+ @UiThreadTest
+ public void testAndroidResourceTag() {
+ LinearLayout linearLayout = (LinearLayout) mBinder.getRoot();
+ TextView view = (TextView) linearLayout.getChildAt(3);
+ String expectedValue = view.getResources().getString(android.R.string.ok);
+ assertEquals(expectedValue, view.getTag());
+ }
+
+ @UiThreadTest
+ public void testIdOnly() {
+ assertEquals("hello", mBinder.textView.getText().toString());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ObservableArrayListTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ObservableArrayListTest.java
new file mode 100644
index 0000000..739ea7a
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ObservableArrayListTest.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.ObservableArrayList;
+import android.databinding.testapp.databinding.BasicBindingBinding;
+
+import android.databinding.ObservableList;
+import android.databinding.OnListChangedListener;
+
+import java.util.ArrayList;
+
+public class ObservableArrayListTest extends BaseDataBinderTest<BasicBindingBinding> {
+
+ private static final int ALL = 0;
+
+ private static final int CHANGE = 1;
+
+ private static final int INSERT = 2;
+
+ private static final int MOVE = 3;
+
+ private static final int REMOVE = 4;
+
+ private ObservableList<String> mObservable;
+
+ private ArrayList<ListChange> mNotifications = new ArrayList<>();
+
+ private OnListChangedListener mListener = new OnListChangedListener() {
+ @Override
+ public void onChanged() {
+ mNotifications.add(new ListChange(ALL, 0, 0));
+ }
+
+ @Override
+ public void onItemRangeChanged(int start, int count) {
+ mNotifications.add(new ListChange(CHANGE, start, count));
+ }
+
+ @Override
+ public void onItemRangeInserted(int start, int count) {
+ mNotifications.add(new ListChange(INSERT, start, count));
+ }
+
+ @Override
+ public void onItemRangeMoved(int from, int to, int count) {
+ mNotifications.add(new ListChange(MOVE, from, to, count));
+ }
+
+ @Override
+ public void onItemRangeRemoved(int start, int count) {
+ mNotifications.add(new ListChange(REMOVE, start, count));
+ }
+ };
+
+ private static class ListChange {
+
+ public ListChange(int change, int start, int count) {
+ this.start = start;
+ this.count = count;
+ this.from = 0;
+ this.to = 0;
+ this.change = change;
+ }
+
+ public ListChange(int change, int from, int to, int count) {
+ this.from = from;
+ this.to = to;
+ this.count = count;
+ this.start = 0;
+ this.change = change;
+ }
+
+ public final int start;
+
+ public final int count;
+
+ public final int from;
+
+ public final int to;
+
+ public final int change;
+ }
+
+ public ObservableArrayListTest() {
+ super(BasicBindingBinding.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ mNotifications.clear();
+ mObservable = new ObservableArrayList<>();
+ }
+
+ public void testAddListener() {
+ mObservable.add("Hello");
+ assertTrue(mNotifications.isEmpty());
+ mObservable.addOnListChangedListener(mListener);
+ mObservable.add("World");
+ assertFalse(mNotifications.isEmpty());
+ }
+
+ public void testRemoveListener() {
+ // test there is no exception when the listener isn't there
+ mObservable.removeOnListChangedListener(mListener);
+
+ mObservable.addOnListChangedListener(mListener);
+ mObservable.add("Hello");
+ mNotifications.clear();
+ mObservable.removeOnListChangedListener(mListener);
+ mObservable.add("World");
+ assertTrue(mNotifications.isEmpty());
+
+ // test there is no exception when the listener isn't there
+ mObservable.removeOnListChangedListener(mListener);
+ }
+
+ public void testAdd() {
+ mObservable.addOnListChangedListener(mListener);
+ mObservable.add("Hello");
+ assertEquals(1, mNotifications.size());
+ ListChange change = mNotifications.get(0);
+ assertEquals(INSERT, change.change);
+ assertEquals(0, change.start);
+ assertEquals(1, change.count);
+ assertEquals("Hello", mObservable.get(0));
+ }
+
+ public void testInsert() {
+ mObservable.addOnListChangedListener(mListener);
+ mObservable.add("Hello");
+ mObservable.add(0, "World");
+ mObservable.add(1, "Dang");
+ mObservable.add(3, "End");
+ assertEquals(4, mObservable.size());
+ assertEquals("World", mObservable.get(0));
+ assertEquals("Dang", mObservable.get(1));
+ assertEquals("Hello", mObservable.get(2));
+ assertEquals("End", mObservable.get(3));
+ assertEquals(4, mNotifications.size());
+ ListChange change = mNotifications.get(1);
+ assertEquals(INSERT, change.change);
+ assertEquals(0, change.start);
+ assertEquals(1, change.count);
+ }
+
+ public void testAddAll() {
+ ArrayList<String> toAdd = new ArrayList<>();
+ toAdd.add("Hello");
+ toAdd.add("World");
+ mObservable.add("First");
+ mObservable.addOnListChangedListener(mListener);
+ mObservable.addAll(toAdd);
+ assertEquals(3, mObservable.size());
+ assertEquals("Hello", mObservable.get(1));
+ assertEquals("World", mObservable.get(2));
+ assertEquals(1, mNotifications.size());
+ ListChange change = mNotifications.get(0);
+ assertEquals(INSERT, change.change);
+ assertEquals(1, change.start);
+ assertEquals(2, change.count);
+ }
+
+ public void testInsertAll() {
+ ArrayList<String> toAdd = new ArrayList<>();
+ toAdd.add("Hello");
+ toAdd.add("World");
+ mObservable.add("First");
+ mObservable.addOnListChangedListener(mListener);
+ mObservable.addAll(0, toAdd);
+ assertEquals(3, mObservable.size());
+ assertEquals("Hello", mObservable.get(0));
+ assertEquals("World", mObservable.get(1));
+ assertEquals(1, mNotifications.size());
+ ListChange change = mNotifications.get(0);
+ assertEquals(INSERT, change.change);
+ assertEquals(0, change.start);
+ assertEquals(2, change.count);
+ }
+
+ public void testClear() {
+ mObservable.add("Hello");
+ mObservable.add("World");
+ mObservable.addOnListChangedListener(mListener);
+ mObservable.clear();
+ assertEquals(1, mNotifications.size());
+ ListChange change = mNotifications.get(0);
+ assertEquals(REMOVE, change.change);
+ assertEquals(0, change.start);
+ assertEquals(2, change.count);
+
+ mObservable.clear();
+ // No notification when nothing is cleared.
+ assertEquals(1, mNotifications.size());
+ }
+
+ public void testRemoveIndex() {
+ mObservable.add("Hello");
+ mObservable.add("World");
+ mObservable.addOnListChangedListener(mListener);
+ assertEquals("Hello", mObservable.remove(0));
+ assertEquals(1, mNotifications.size());
+ ListChange change = mNotifications.get(0);
+ assertEquals(REMOVE, change.change);
+ assertEquals(0, change.start);
+ assertEquals(1, change.count);
+ }
+
+ public void testRemoveObject() {
+ mObservable.add("Hello");
+ mObservable.add("World");
+ mObservable.addOnListChangedListener(mListener);
+ assertTrue(mObservable.remove("Hello"));
+ assertEquals(1, mNotifications.size());
+ ListChange change = mNotifications.get(0);
+ assertEquals(REMOVE, change.change);
+ assertEquals(0, change.start);
+ assertEquals(1, change.count);
+
+ assertFalse(mObservable.remove("Hello"));
+ // nothing removed, don't notify
+ assertEquals(1, mNotifications.size());
+ }
+
+ public void testSet() {
+ mObservable.add("Hello");
+ mObservable.add("World");
+ mObservable.addOnListChangedListener(mListener);
+ assertEquals("Hello", mObservable.set(0, "Goodbye"));
+ assertEquals("Goodbye", mObservable.get(0));
+ assertEquals(2, mObservable.size());
+ ListChange change = mNotifications.get(0);
+ assertEquals(CHANGE, change.change);
+ assertEquals(0, change.start);
+ assertEquals(1, change.count);
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ObservableArrayMapTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ObservableArrayMapTest.java
new file mode 100644
index 0000000..fc5f689
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ObservableArrayMapTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.ObservableArrayMap;
+import android.databinding.testapp.databinding.BasicBindingBinding;
+
+import android.databinding.ObservableMap;
+import android.databinding.OnMapChangedListener;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.util.SimpleArrayMap;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+public class ObservableArrayMapTest extends BaseDataBinderTest<BasicBindingBinding> {
+
+ private ObservableArrayMap<String, String> mObservable;
+
+ private ArrayList<String> mNotifications = new ArrayList<>();
+
+ private OnMapChangedListener mListener = new OnMapChangedListener() {
+ @Override
+ public void onMapChanged(ObservableMap observableMap, Object o) {
+ assertEquals(mObservable, observableMap);
+ mNotifications.add((String) o);
+ }
+ };
+
+ public ObservableArrayMapTest() {
+ super(BasicBindingBinding.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ mNotifications.clear();
+ mObservable = new ObservableArrayMap<>();
+ }
+
+ public void testAddListener() {
+ mObservable.put("Hello", "World");
+ assertTrue(mNotifications.isEmpty());
+ mObservable.addOnMapChangedListener(mListener);
+ mObservable.put("Hello", "Goodbye");
+ assertFalse(mNotifications.isEmpty());
+ }
+
+ public void testRemoveListener() {
+ // test there is no exception when the listener isn't there
+ mObservable.removeOnMapChangedListener(mListener);
+
+ mObservable.addOnMapChangedListener(mListener);
+ mObservable.put("Hello", "World");
+ mNotifications.clear();
+ mObservable.removeOnMapChangedListener(mListener);
+ mObservable.put("World", "Hello");
+ assertTrue(mNotifications.isEmpty());
+
+ // test there is no exception when the listener isn't there
+ mObservable.removeOnMapChangedListener(mListener);
+ }
+
+ public void testClear() {
+ mObservable.put("Hello", "World");
+ mObservable.put("World", "Hello");
+ mObservable.addOnMapChangedListener(mListener);
+ mObservable.clear();
+ assertEquals(1, mNotifications.size());
+ assertNull(mNotifications.get(0));
+ assertEquals(0, mObservable.size());
+ assertTrue(mObservable.isEmpty());
+
+ mObservable.clear();
+ // No notification when nothing is cleared.
+ assertEquals(1, mNotifications.size());
+ }
+
+ public void testPut() {
+ mObservable.addOnMapChangedListener(mListener);
+ mObservable.put("Hello", "World");
+ assertEquals(1, mNotifications.size());
+ assertEquals("Hello", mNotifications.get(0));
+ assertEquals("World", mObservable.get("Hello"));
+
+ mObservable.put("Hello", "World2");
+ assertEquals(2, mNotifications.size());
+ assertEquals("Hello", mNotifications.get(1));
+ assertEquals("World2", mObservable.get("Hello"));
+
+ mObservable.put("World", "Hello");
+ assertEquals(3, mNotifications.size());
+ assertEquals("World", mNotifications.get(2));
+ assertEquals("Hello", mObservable.get("World"));
+ }
+
+ public void testPutAll() {
+ Map<String, String> toAdd = new ArrayMap<>();
+ toAdd.put("Hello", "World");
+ toAdd.put("Goodbye", "Cruel World");
+ mObservable.put("Cruel", "World");
+ mObservable.addOnMapChangedListener(mListener);
+ mObservable.putAll(toAdd);
+ assertEquals(3, mObservable.size());
+ assertEquals("World", mObservable.get("Hello"));
+ assertEquals("Cruel World", mObservable.get("Goodbye"));
+ assertEquals(2, mNotifications.size());
+ // order is not guaranteed
+ assertTrue(mNotifications.contains("Hello"));
+ assertTrue(mNotifications.contains("Goodbye"));
+ }
+
+ public void testPutAllSimpleArrayMap() {
+ SimpleArrayMap<String, String> toAdd = new ArrayMap<>();
+ toAdd.put("Hello", "World");
+ toAdd.put("Goodbye", "Cruel World");
+ mObservable.put("Cruel", "World");
+ mObservable.addOnMapChangedListener(mListener);
+ mObservable.putAll(toAdd);
+ assertEquals(3, mObservable.size());
+ assertEquals("World", mObservable.get("Hello"));
+ assertEquals("Cruel World", mObservable.get("Goodbye"));
+ assertEquals(2, mNotifications.size());
+ // order is not guaranteed
+ assertTrue(mNotifications.contains("Hello"));
+ assertTrue(mNotifications.contains("Goodbye"));
+ }
+
+ public void testRemove() {
+ mObservable.put("Hello", "World");
+ mObservable.put("Goodbye", "Cruel World");
+ mObservable.addOnMapChangedListener(mListener);
+ assertEquals("World", mObservable.remove("Hello"));
+ assertEquals(1, mNotifications.size());
+ assertEquals("Hello", mNotifications.get(0));
+
+ assertNull(mObservable.remove("Hello"));
+ // nothing removed, don't notify
+ assertEquals(1, mNotifications.size());
+ }
+
+ public void testRemoveAll() {
+ ArrayList<String> toRemove = new ArrayList<>();
+ toRemove.add("Hello");
+ toRemove.add("Goodbye");
+ mObservable.put("Hello", "World");
+ mObservable.put("Goodbye", "Cruel World");
+ mObservable.put("Cruel", "World");
+ mObservable.addOnMapChangedListener(mListener);
+ assertTrue(mObservable.removeAll(toRemove));
+ assertEquals(2, mNotifications.size());
+ // order is not guaranteed
+ assertTrue(mNotifications.contains("Hello"));
+ assertTrue(mNotifications.contains("Goodbye"));
+
+ assertTrue(mObservable.containsKey("Cruel"));
+
+ // Test nothing removed
+ assertFalse(mObservable.removeAll(toRemove));
+ assertEquals(2, mNotifications.size());
+ }
+
+ public void testRetainAll() {
+ ArrayList<String> toRetain = new ArrayList<>();
+ toRetain.add("Hello");
+ toRetain.add("Goodbye");
+ mObservable.put("Hello", "World");
+ mObservable.put("Goodbye", "Cruel World");
+ mObservable.put("Cruel", "World");
+ mObservable.addOnMapChangedListener(mListener);
+ assertTrue(mObservable.retainAll(toRetain));
+ assertEquals(1, mNotifications.size());
+ assertEquals("Cruel", mNotifications.get(0));
+ assertTrue(mObservable.containsKey("Hello"));
+ assertTrue(mObservable.containsKey("Goodbye"));
+
+ // Test nothing removed
+ assertFalse(mObservable.retainAll(toRetain));
+ assertEquals(1, mNotifications.size());
+ }
+
+ public void testRemoveAt() {
+ mObservable.put("Hello", "World");
+ mObservable.put("Goodbye", "Cruel World");
+ mObservable.addOnMapChangedListener(mListener);
+ String key = mObservable.keyAt(0);
+ String value = mObservable.valueAt(0);
+ assertTrue("Hello".equals(key) || "Goodbye".equals(key));
+ assertEquals(value, mObservable.removeAt(0));
+ assertEquals(1, mNotifications.size());
+ assertEquals(key, mNotifications.get(0));
+ }
+
+ public void testSetValueAt() {
+ mObservable.put("Hello", "World");
+ mObservable.addOnMapChangedListener(mListener);
+ assertEquals("World", mObservable.setValueAt(0, "Cruel World"));
+ assertEquals(1, mNotifications.size());
+ assertEquals("Hello", mNotifications.get(0));
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ObservableFieldTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ObservableFieldTest.java
new file mode 100644
index 0000000..7b29122
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ObservableFieldTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.ObservableFieldTestBinding;
+import android.databinding.testapp.vo.ObservableFieldBindingObject;
+
+import android.test.UiThreadTest;
+import android.widget.TextView;
+
+public class ObservableFieldTest extends BaseDataBinderTest<ObservableFieldTestBinding> {
+ private ObservableFieldBindingObject mObj;
+
+ public ObservableFieldTest() {
+ super(ObservableFieldTestBinding.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ try {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mObj = new ObservableFieldBindingObject();
+ mBinder.setObj(mObj);
+ mBinder.executePendingBindings();
+ }
+ });
+ } catch (Throwable throwable) {
+ throw new Exception(throwable);
+ }
+ }
+
+ @UiThreadTest
+ public void testBoolean() {
+ TextView view = mBinder.bField;
+ assertEquals("false", view.getText());
+
+ mObj.bField.set(true);
+ mBinder.executePendingBindings();
+
+ assertEquals("true", view.getText());
+ }
+
+ @UiThreadTest
+ public void testByte() {
+ TextView view = mBinder.tField;
+ assertEquals("0", view.getText());
+
+ mObj.tField.set((byte) 1);
+ mBinder.executePendingBindings();
+
+ assertEquals("1", view.getText());
+ }
+
+ @UiThreadTest
+ public void testShort() {
+ TextView view = mBinder.sField;
+ assertEquals("0", view.getText());
+
+ mObj.sField.set((short) 1);
+ mBinder.executePendingBindings();
+
+ assertEquals("1", view.getText());
+ }
+
+ @UiThreadTest
+ public void testChar() {
+ TextView view = mBinder.cField;
+ assertEquals("\u0000", view.getText());
+
+ mObj.cField.set('A');
+ mBinder.executePendingBindings();
+
+ assertEquals("A", view.getText());
+ }
+
+ @UiThreadTest
+ public void testInt() {
+ TextView view = mBinder.iField;
+ assertEquals("0", view.getText());
+
+ mObj.iField.set(1);
+ mBinder.executePendingBindings();
+
+ assertEquals("1", view.getText());
+ }
+
+ @UiThreadTest
+ public void testLong() {
+ TextView view = mBinder.lField;
+ assertEquals("0", view.getText());
+
+ mObj.lField.set(1);
+ mBinder.executePendingBindings();
+
+ assertEquals("1", view.getText());
+ }
+
+ @UiThreadTest
+ public void testFloat() {
+ TextView view = mBinder.fField;
+ assertEquals("0.0", view.getText());
+
+ mObj.fField.set(1);
+ mBinder.executePendingBindings();
+
+ assertEquals("1.0", view.getText());
+ }
+
+ @UiThreadTest
+ public void testDouble() {
+ TextView view = mBinder.dField;
+ assertEquals("0.0", view.getText());
+
+ mObj.dField.set(1);
+ mBinder.executePendingBindings();
+
+ assertEquals("1.0", view.getText());
+ }
+
+ @UiThreadTest
+ public void testObject() {
+ TextView view = mBinder.oField;
+ assertEquals("Hello", view.getText());
+
+ mObj.oField.set("World");
+ mBinder.executePendingBindings();
+
+ assertEquals("World", view.getText());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ObservableWithNotBindableFieldObjectTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ObservableWithNotBindableFieldObjectTest.java
new file mode 100644
index 0000000..b14f413
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ObservableWithNotBindableFieldObjectTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.databinding.testapp.BaseDataBinderTest;
+import android.databinding.testapp.R;
+import android.databinding.testapp.databinding.ObservableWithNotBindableFieldBinding;
+import android.databinding.testapp.vo.ObservableWithNotBindableFieldObject;
+
+import android.test.UiThreadTest;
+
+public class ObservableWithNotBindableFieldObjectTest extends BaseDataBinderTest<ObservableWithNotBindableFieldBinding> {
+
+
+ public ObservableWithNotBindableFieldObjectTest() {
+ super(ObservableWithNotBindableFieldBinding.class);
+ }
+
+ @UiThreadTest
+ public void testSimple() {
+ ObservableWithNotBindableFieldObject obj = new ObservableWithNotBindableFieldObject();
+ mBinder.setObj(obj);
+ mBinder.executePendingBindings();
+ assertEquals("", mBinder.textView.getText().toString());
+ obj.update("100");
+ mBinder.executePendingBindings();
+ assertEquals("100", mBinder.textView.getText().toString());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ProcessBindableTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ProcessBindableTest.java
new file mode 100644
index 0000000..4325213
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ProcessBindableTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.BasicBindingBinding;
+
+import android.util.ArrayMap;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+import android.databinding.testapp.BR;
+public class ProcessBindableTest extends BaseDataBinderTest<BasicBindingBinding> {
+ private static String[] EXPECTED_BINDING_NAMES = {
+ "bindableField1",
+ "bindableField2",
+ "bindableField3",
+ "bindableField4",
+ "mbindableField5",
+ "bindableField6",
+ "bindableField7",
+ "bindableField8",
+ };
+
+ public ProcessBindableTest() {
+ super(BasicBindingBinding.class);
+ }
+
+ public void testFieldsGenerated() throws IllegalAccessException {
+ Field[] fields = BR.class.getFields();
+
+ ArrayMap<String, Integer> fieldValues = new ArrayMap<>();
+ int modifiers = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL;
+ for (Field field: fields) {
+ assertTrue(field.getModifiers() == modifiers);
+ String name = field.getName();
+ fieldValues.put(name, field.getInt(null));
+ }
+
+ assertTrue(fieldValues.containsKey("_all"));
+ assertEquals(0, (int) fieldValues.get("_all"));
+ HashSet<Integer> values = new HashSet<>();
+ values.add(0);
+
+ for (String fieldName : EXPECTED_BINDING_NAMES) {
+ assertTrue("missing field: " + fieldName, fieldValues.containsKey(fieldName));
+ assertFalse(values.contains(fieldValues.get(fieldName)));
+ values.add(fieldValues.get(fieldName));
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ProgressBarBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ProgressBarBindingAdapterTest.java
new file mode 100644
index 0000000..52d558f
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ProgressBarBindingAdapterTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.ProgressBarAdapterTestBinding;
+import android.databinding.testapp.vo.ProgressBarBindingObject;
+
+import android.os.Build;
+import android.widget.ProgressBar;
+
+public class ProgressBarBindingAdapterTest
+ extends BindingAdapterTestBase<ProgressBarAdapterTestBinding, ProgressBarBindingObject> {
+
+ ProgressBar mView;
+
+ public ProgressBarBindingAdapterTest() {
+ super(ProgressBarAdapterTestBinding.class, ProgressBarBindingObject.class,
+ R.layout.progress_bar_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testTint() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ assertEquals(mBindingObject.getIndeterminateTint(),
+ mView.getIndeterminateTintList().getDefaultColor());
+ assertEquals(mBindingObject.getProgressTint(),
+ mView.getProgressTintList().getDefaultColor());
+ assertEquals(mBindingObject.getSecondaryProgressTint(),
+ mView.getSecondaryProgressTintList().getDefaultColor());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getIndeterminateTint(),
+ mView.getIndeterminateTintList().getDefaultColor());
+ assertEquals(mBindingObject.getProgressTint(),
+ mView.getProgressTintList().getDefaultColor());
+ assertEquals(mBindingObject.getSecondaryProgressTint(),
+ mView.getSecondaryProgressTintList().getDefaultColor());
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/PropertyChangeRegistryTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/PropertyChangeRegistryTest.java
new file mode 100644
index 0000000..44b7dcf
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/PropertyChangeRegistryTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.PropertyChangeRegistry;
+import android.databinding.testapp.databinding.BasicBindingBinding;
+
+import android.databinding.Observable;
+import android.databinding.OnPropertyChangedListener;
+
+public class PropertyChangeRegistryTest extends BaseDataBinderTest<BasicBindingBinding> {
+
+ private int notificationCount = 0;
+
+ public PropertyChangeRegistryTest() {
+ super(BasicBindingBinding.class);
+ }
+
+ public void testNotifyChanged() {
+ PropertyChangeRegistry propertyChangeRegistry = new PropertyChangeRegistry();
+
+ final Observable observableObj = new Observable() {
+ @Override
+ public void addOnPropertyChangedListener(
+ OnPropertyChangedListener onPropertyChangedListener) {
+ }
+
+ @Override
+ public void removeOnPropertyChangedListener(
+ OnPropertyChangedListener onPropertyChangedListener) {
+ }
+ };
+
+ final int expectedId = 100;
+ OnPropertyChangedListener listener = new OnPropertyChangedListener() {
+ @Override
+ public void onPropertyChanged(Observable observable, int id) {
+ notificationCount++;
+ assertEquals(expectedId, id);
+ assertEquals(observableObj, observable);
+ }
+ };
+ propertyChangeRegistry.add(listener);
+
+ assertEquals(0, notificationCount);
+ propertyChangeRegistry.notifyChange(observableObj, expectedId);
+ assertEquals(1, notificationCount);
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/RadioGroupBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/RadioGroupBindingAdapterTest.java
new file mode 100644
index 0000000..7ab4a0d
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/RadioGroupBindingAdapterTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.RadioGroupAdapterTestBinding;
+import android.databinding.testapp.vo.RadioGroupBindingObject;
+
+import android.widget.RadioGroup;
+
+public class RadioGroupBindingAdapterTest
+ extends BindingAdapterTestBase<RadioGroupAdapterTestBinding, RadioGroupBindingObject> {
+
+ RadioGroup mView;
+
+ public RadioGroupBindingAdapterTest() {
+ super(RadioGroupAdapterTestBinding.class, RadioGroupBindingObject.class,
+ R.layout.radio_group_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testRadioGroup() throws Throwable {
+ assertEquals(mBindingObject.getCheckedButton(), mView.getCheckedRadioButtonId());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getCheckedButton(), mView.getCheckedRadioButtonId());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ReadComplexTernaryTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ReadComplexTernaryTest.java
new file mode 100644
index 0000000..6970075
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ReadComplexTernaryTest.java
@@ -0,0 +1,22 @@
+package android.databinding.testapp;
+
+import android.test.UiThreadTest;
+import android.databinding.testapp.databinding.ReadComplexTernaryBinding;
+
+import android.databinding.testapp.vo.User;
+
+public class ReadComplexTernaryTest extends BaseDataBinderTest<ReadComplexTernaryBinding> {
+ public ReadComplexTernaryTest() {
+ super(ReadComplexTernaryBinding.class);
+ }
+
+ @UiThreadTest
+ public void testWhenNull() {
+ User user = new User();
+ user.setName("a");
+ user.setFullName("a b");
+ mBinder.setUser(user);
+ mBinder.executePendingBindings();
+ assertEquals("?", mBinder.textView.getText().toString());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ResourceTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ResourceTest.java
new file mode 100644
index 0000000..f48bb2b
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ResourceTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.ResourceTestBinding;
+
+import android.test.UiThreadTest;
+import android.widget.TextView;
+
+public class ResourceTest extends BaseDataBinderTest<ResourceTestBinding> {
+
+ public ResourceTest() {
+ super(ResourceTestBinding.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mBinder.setCount(0);
+ mBinder.setTitle("Mrs.");
+ mBinder.setLastName("Doubtfire");
+ mBinder.setBase(2);
+ mBinder.setPbase(3);
+ try {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mBinder.executePendingBindings();
+ }
+ });
+ } catch (Throwable throwable) {
+ throw new Exception(throwable);
+ }
+ }
+
+ @UiThreadTest
+ public void testStringFormat() throws Throwable {
+ TextView view = mBinder.textView0;
+ assertEquals("Mrs. Doubtfire", view.getText().toString());
+
+ mBinder.setTitle("Mr.");
+ mBinder.executePendingBindings();
+ assertEquals("Mr. Doubtfire", view.getText().toString());
+ }
+
+ @UiThreadTest
+ public void testQuantityString() throws Throwable {
+ TextView view = mBinder.textView1;
+ assertEquals("oranges", view.getText().toString());
+
+ mBinder.setCount(1);
+ mBinder.executePendingBindings();
+ assertEquals("orange", view.getText().toString());
+ }
+
+ @UiThreadTest
+ public void testFractionNoParameters() throws Throwable {
+ TextView view = mBinder.fractionNoParameters;
+ assertEquals("1.5", view.getText().toString());
+ }
+
+ @UiThreadTest
+ public void testFractionOneParameter() throws Throwable {
+ TextView view = mBinder.fractionOneParameter;
+ assertEquals("3.0", view.getText().toString());
+ }
+
+ @UiThreadTest
+ public void testFractionTwoParameters() throws Throwable {
+ TextView view = mBinder.fractionTwoParameters;
+ assertEquals("9.0", view.getText().toString());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/SpinnerBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/SpinnerBindingAdapterTest.java
new file mode 100644
index 0000000..384ff21
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/SpinnerBindingAdapterTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.SpinnerAdapterTestBinding;
+import android.databinding.testapp.vo.SpinnerBindingObject;
+
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.widget.Spinner;
+
+public class SpinnerBindingAdapterTest
+ extends BindingAdapterTestBase<SpinnerAdapterTestBinding, SpinnerBindingObject> {
+
+ Spinner mView;
+
+ public SpinnerBindingAdapterTest() {
+ super(SpinnerAdapterTestBinding.class, SpinnerBindingObject.class,
+ R.layout.spinner_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testSpinner() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ assertEquals(mBindingObject.getPopupBackground(),
+ ((ColorDrawable) mView.getPopupBackground()).getColor());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getPopupBackground(),
+ ((ColorDrawable) mView.getPopupBackground()).getColor());
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/SwitchBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/SwitchBindingAdapterTest.java
new file mode 100644
index 0000000..65f38ba
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/SwitchBindingAdapterTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.SwitchAdapterTestBinding;
+import android.databinding.testapp.vo.SwitchBindingObject;
+
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.widget.Switch;
+
+public class SwitchBindingAdapterTest
+ extends BindingAdapterTestBase<SwitchAdapterTestBinding, SwitchBindingObject> {
+
+ Switch mView;
+
+ public SwitchBindingAdapterTest() {
+ super(SwitchAdapterTestBinding.class, SwitchBindingObject.class,
+ R.layout.switch_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testSwitch() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ assertEquals(mBindingObject.getThumb(),
+ ((ColorDrawable) mView.getThumbDrawable()).getColor());
+ assertEquals(mBindingObject.getTrack(),
+ ((ColorDrawable) mView.getTrackDrawable()).getColor());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getThumb(),
+ ((ColorDrawable) mView.getThumbDrawable()).getColor());
+ assertEquals(mBindingObject.getTrack(),
+ ((ColorDrawable) mView.getTrackDrawable()).getColor());
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/TabWidgetBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/TabWidgetBindingAdapterTest.java
new file mode 100644
index 0000000..c72010b
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/TabWidgetBindingAdapterTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.TabWidgetAdapterTestBinding;
+import android.databinding.testapp.vo.TabWidgetBindingObject;
+
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.widget.TabWidget;
+
+public class TabWidgetBindingAdapterTest
+ extends BindingAdapterTestBase<TabWidgetAdapterTestBinding, TabWidgetBindingObject> {
+
+ TabWidget mView;
+
+ public TabWidgetBindingAdapterTest() {
+ super(TabWidgetAdapterTestBinding.class, TabWidgetBindingObject.class,
+ R.layout.tab_widget_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testStrip() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ assertEquals(mBindingObject.getDivider().getColor(),
+ ((ColorDrawable) mView.getDividerDrawable()).getColor());
+ assertEquals(mBindingObject.isTabStripEnabled(), mView.isStripEnabled());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getDivider().getColor(),
+ ((ColorDrawable) mView.getDividerDrawable()).getColor());
+ assertEquals(mBindingObject.isTabStripEnabled(), mView.isStripEnabled());
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/TableLayoutBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/TableLayoutBindingAdapterTest.java
new file mode 100644
index 0000000..4f00623
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/TableLayoutBindingAdapterTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.TableLayoutAdapterTestBinding;
+import android.databinding.testapp.vo.TableLayoutBindingObject;
+
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.widget.TableLayout;
+
+public class TableLayoutBindingAdapterTest
+ extends BindingAdapterTestBase<TableLayoutAdapterTestBinding, TableLayoutBindingObject> {
+
+ TableLayout mView;
+
+ public TableLayoutBindingAdapterTest() {
+ super(TableLayoutAdapterTestBinding.class, TableLayoutBindingObject.class,
+ R.layout.table_layout_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testDivider() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ assertEquals(mBindingObject.getDivider(),
+ ((ColorDrawable) mView.getDividerDrawable()).getColor());
+ changeValues();
+ assertEquals(mBindingObject.getDivider(),
+ ((ColorDrawable) mView.getDividerDrawable()).getColor());
+ }
+ }
+
+ public void testColumns() throws Throwable {
+ assertFalse(mView.isColumnCollapsed(0));
+ assertTrue(mView.isColumnCollapsed(1));
+ assertFalse(mView.isColumnCollapsed(2));
+
+ assertFalse(mView.isColumnShrinkable(0));
+ assertTrue(mView.isColumnShrinkable(1));
+ assertFalse(mView.isColumnShrinkable(2));
+
+ assertFalse(mView.isColumnStretchable(0));
+ assertTrue(mView.isColumnStretchable(1));
+ assertFalse(mView.isColumnStretchable(2));
+
+ changeValues();
+
+ assertFalse(mView.isColumnCollapsed(0));
+ assertFalse(mView.isColumnCollapsed(1));
+ assertFalse(mView.isColumnCollapsed(2));
+
+ assertTrue(mView.isColumnShrinkable(0));
+ assertTrue(mView.isColumnShrinkable(1));
+ assertFalse(mView.isColumnShrinkable(2));
+
+ assertTrue(mView.isColumnStretchable(0));
+ assertTrue(mView.isColumnStretchable(1));
+ assertTrue(mView.isColumnStretchable(2));
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/TextViewBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/TextViewBindingAdapterTest.java
new file mode 100644
index 0000000..98272dd
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/TextViewBindingAdapterTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.TextViewAdapterTestBinding;
+import android.databinding.testapp.vo.TextViewBindingObject;
+
+import android.annotation.TargetApi;
+import android.databinding.adapters.TextViewBindingAdapter;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Build;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.InputType;
+import android.text.Spannable;
+import android.text.method.DialerKeyListener;
+import android.text.method.DigitsKeyListener;
+import android.text.method.KeyListener;
+import android.text.method.TextKeyListener;
+import android.widget.TextView;
+
+public class TextViewBindingAdapterTest
+ extends BindingAdapterTestBase<TextViewAdapterTestBinding, TextViewBindingObject> {
+
+ public TextViewBindingAdapterTest() {
+ super(TextViewAdapterTestBinding.class, TextViewBindingObject.class,
+ R.layout.text_view_adapter_test);
+ }
+
+ public void testNumeric() throws Throwable {
+ TextView view = mBinder.numericText;
+ assertTrue(view.getKeyListener() instanceof DigitsKeyListener);
+ DigitsKeyListener listener = (DigitsKeyListener) view.getKeyListener();
+ assertEquals(getExpectedNumericType(), listener.getInputType());
+
+ changeValues();
+
+ assertTrue(view.getKeyListener() instanceof DigitsKeyListener);
+ listener = (DigitsKeyListener) view.getKeyListener();
+ assertEquals(getExpectedNumericType(), listener.getInputType());
+ }
+
+ private int getExpectedNumericType() {
+ int expectedType = InputType.TYPE_CLASS_NUMBER;
+ if ((mBindingObject.getNumeric() & TextViewBindingAdapter.SIGNED) != 0) {
+ expectedType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
+ }
+ if ((mBindingObject.getNumeric() & TextViewBindingAdapter.DECIMAL) != 0) {
+ expectedType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
+ }
+ return expectedType;
+ }
+
+ public void testDrawables() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ TextView view = mBinder.textDrawableNormal;
+ assertEquals(mBindingObject.getDrawableLeft(),
+ ((ColorDrawable) view.getCompoundDrawables()[0]).getColor());
+ assertEquals(mBindingObject.getDrawableTop(),
+ ((ColorDrawable) view.getCompoundDrawables()[1]).getColor());
+ assertEquals(mBindingObject.getDrawableRight(),
+ ((ColorDrawable) view.getCompoundDrawables()[2]).getColor());
+ assertEquals(mBindingObject.getDrawableBottom(),
+ ((ColorDrawable) view.getCompoundDrawables()[3]).getColor());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getDrawableLeft(),
+ ((ColorDrawable) view.getCompoundDrawables()[0]).getColor());
+ assertEquals(mBindingObject.getDrawableTop(),
+ ((ColorDrawable) view.getCompoundDrawables()[1]).getColor());
+ assertEquals(mBindingObject.getDrawableRight(),
+ ((ColorDrawable) view.getCompoundDrawables()[2]).getColor());
+ assertEquals(mBindingObject.getDrawableBottom(),
+ ((ColorDrawable) view.getCompoundDrawables()[3]).getColor());
+ }
+ }
+
+ public void testDrawableStartEnd() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ TextView view = mBinder.textDrawableStartEnd;
+ assertEquals(mBindingObject.getDrawableStart(),
+ ((ColorDrawable) view.getCompoundDrawablesRelative()[0]).getColor());
+ assertEquals(mBindingObject.getDrawableEnd(),
+ ((ColorDrawable) view.getCompoundDrawablesRelative()[2]).getColor());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getDrawableStart(),
+ ((ColorDrawable) view.getCompoundDrawablesRelative()[0]).getColor());
+ assertEquals(mBindingObject.getDrawableEnd(),
+ ((ColorDrawable) view.getCompoundDrawablesRelative()[2]).getColor());
+ }
+ }
+
+ public void testSimpleProperties() throws Throwable {
+ TextView view = mBinder.textView;
+
+ assertEquals(mBindingObject.getAutoLink(), view.getAutoLinkMask());
+ assertEquals(mBindingObject.getDrawablePadding(), view.getCompoundDrawablePadding());
+ assertEquals(mBindingObject.getTextSize(), view.getTextSize());
+ assertEquals(mBindingObject.getTextColorHint(), view.getHintTextColors().getDefaultColor());
+ assertEquals(mBindingObject.getTextColorLink(), view.getLinkTextColors().getDefaultColor());
+ assertEquals(mBindingObject.isAutoText(), isAutoTextEnabled(view));
+ assertEquals(mBindingObject.getCapitalize(), getCapitalization(view));
+ assertEquals(mBindingObject.getImeActionLabel(), view.getImeActionLabel());
+ assertEquals(mBindingObject.getImeActionId(), view.getImeActionId());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ assertEquals(mBindingObject.getTextColorHighlight(), view.getHighlightColor());
+ assertEquals(mBindingObject.getLineSpacingExtra(), view.getLineSpacingExtra());
+ assertEquals(mBindingObject.getLineSpacingMultiplier(),
+ view.getLineSpacingMultiplier());
+ assertEquals(mBindingObject.getShadowColor(), view.getShadowColor());
+ assertEquals(mBindingObject.getShadowDx(), view.getShadowDx());
+ assertEquals(mBindingObject.getShadowDy(), view.getShadowDy());
+ assertEquals(mBindingObject.getShadowRadius(), view.getShadowRadius());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ assertEquals(mBindingObject.getMaxLength(), getMaxLength(view));
+ }
+ }
+
+ changeValues();
+
+ assertEquals(mBindingObject.getAutoLink(), view.getAutoLinkMask());
+ assertEquals(mBindingObject.getDrawablePadding(), view.getCompoundDrawablePadding());
+ assertEquals(mBindingObject.getTextSize(), view.getTextSize());
+ assertEquals(mBindingObject.getTextColorHint(), view.getHintTextColors().getDefaultColor());
+ assertEquals(mBindingObject.getTextColorLink(), view.getLinkTextColors().getDefaultColor());
+ assertEquals(mBindingObject.isAutoText(), isAutoTextEnabled(view));
+ assertEquals(mBindingObject.getCapitalize(), getCapitalization(view));
+ assertEquals(mBindingObject.getImeActionLabel(), view.getImeActionLabel());
+ assertEquals(mBindingObject.getImeActionId(), view.getImeActionId());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ assertEquals(mBindingObject.getTextColorHighlight(), view.getHighlightColor());
+ assertEquals(mBindingObject.getLineSpacingExtra(), view.getLineSpacingExtra());
+ assertEquals(mBindingObject.getLineSpacingMultiplier(),
+ view.getLineSpacingMultiplier());
+ assertEquals(mBindingObject.getShadowColor(), view.getShadowColor());
+ assertEquals(mBindingObject.getShadowDx(), view.getShadowDx());
+ assertEquals(mBindingObject.getShadowDy(), view.getShadowDy());
+ assertEquals(mBindingObject.getShadowRadius(), view.getShadowRadius());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ assertEquals(mBindingObject.getMaxLength(), getMaxLength(view));
+ }
+ }
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mBindingObject.setCapitalize(TextKeyListener.Capitalize.CHARACTERS);
+ mBinder.executePendingBindings();
+ }
+ });
+
+ assertEquals(mBindingObject.getCapitalize(), getCapitalization(view));
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mBindingObject.setCapitalize(TextKeyListener.Capitalize.WORDS);
+ mBinder.executePendingBindings();
+ }
+ });
+
+ assertEquals(mBindingObject.getCapitalize(), getCapitalization(view));
+ }
+
+ private static boolean isAutoTextEnabled(TextView view) {
+ KeyListener keyListener = view.getKeyListener();
+ if (keyListener == null) {
+ return false;
+ }
+ if (!(keyListener instanceof TextKeyListener)) {
+ return false;
+ }
+ TextKeyListener textKeyListener = (TextKeyListener) keyListener;
+ return ((textKeyListener.getInputType() & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0);
+ }
+
+ private static TextKeyListener.Capitalize getCapitalization(TextView view) {
+ KeyListener keyListener = view.getKeyListener();
+ if (keyListener == null) {
+ return TextKeyListener.Capitalize.NONE;
+ }
+ int inputType = keyListener.getInputType();
+ if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
+ return TextKeyListener.Capitalize.CHARACTERS;
+ } else if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
+ return TextKeyListener.Capitalize.WORDS;
+ } else if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
+ return TextKeyListener.Capitalize.SENTENCES;
+ } else {
+ return TextKeyListener.Capitalize.NONE;
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static int getMaxLength(TextView view) {
+ InputFilter[] filters = view.getFilters();
+ for (InputFilter filter : filters) {
+ if (filter instanceof InputFilter.LengthFilter) {
+ InputFilter.LengthFilter lengthFilter = (InputFilter.LengthFilter) filter;
+ return lengthFilter.getMax();
+ }
+ }
+ return -1;
+ }
+
+ public void testAllCaps() throws Throwable {
+ TextView view = mBinder.textAllCaps;
+
+ assertEquals(mBindingObject.isTextAllCaps(), view.getTransformationMethod() != null);
+ if (view.getTransformationMethod() != null) {
+ assertEquals("ALL CAPS",
+ view.getTransformationMethod().getTransformation("all caps", view));
+ }
+
+ changeValues();
+
+ assertEquals(mBindingObject.isTextAllCaps(), view.getTransformationMethod() != null);
+ if (view.getTransformationMethod() != null) {
+ assertEquals("ALL CAPS",
+ view.getTransformationMethod().getTransformation("all caps", view));
+ }
+ }
+
+ public void testBufferType() throws Throwable {
+ TextView view = mBinder.textBufferType;
+
+ assertEquals(mBindingObject.getBufferType(), getBufferType(view));
+ changeValues();
+ assertEquals(mBindingObject.getBufferType(), getBufferType(view));
+ }
+
+ private static TextView.BufferType getBufferType(TextView view) {
+ CharSequence text = view.getText();
+ if (text instanceof Editable) {
+ return TextView.BufferType.EDITABLE;
+ }
+ if (text instanceof Spannable) {
+ return TextView.BufferType.SPANNABLE;
+ }
+ return TextView.BufferType.NORMAL;
+ }
+
+ public void testInputType() throws Throwable {
+ TextView view = mBinder.textInputType;
+ assertEquals(mBindingObject.getInputType(), view.getInputType());
+ changeValues();
+ assertEquals(mBindingObject.getInputType(), view.getInputType());
+ }
+
+ public void testDigits() throws Throwable {
+ TextView view = mBinder.textDigits;
+ assertEquals(mBindingObject.getDigits(), getDigits(view));
+ changeValues();
+ assertEquals(mBindingObject.getDigits(), getDigits(view));
+ }
+
+ private static String getDigits(TextView textView) {
+ KeyListener keyListener = textView.getKeyListener();
+ if (!(keyListener instanceof DigitsKeyListener)) {
+ return null;
+ }
+ DigitsKeyListener digitsKeyListener = (DigitsKeyListener) keyListener;
+ String input = "abcdefghijklmnopqrstuvwxyz";
+ Spannable spannable = Spannable.Factory.getInstance().newSpannable(input);
+ return digitsKeyListener.filter(input, 0, input.length(), spannable, 0, input.length())
+ .toString();
+ }
+
+ public void testPhoneNumber() throws Throwable {
+ TextView textView = mBinder.textPhoneNumber;
+ assertEquals(mBindingObject.isPhoneNumber(), isPhoneNumber(textView));
+ changeValues();
+ assertEquals(mBindingObject.isPhoneNumber(), isPhoneNumber(textView));
+ }
+
+ private static boolean isPhoneNumber(TextView view) {
+ KeyListener keyListener = view.getKeyListener();
+ return (keyListener instanceof DialerKeyListener);
+ }
+
+ public void testInputMethod() throws Throwable {
+ TextView textView = mBinder.textInputMethod;
+ assertTrue(TextViewBindingObject.KeyListener1.class.isInstance(textView.getKeyListener()));
+ changeValues();
+ assertTrue(TextViewBindingObject.KeyListener2.class.isInstance(textView.getKeyListener()));
+ }
+
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewBindingAdapterTest.java
new file mode 100644
index 0000000..c9bbb35
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewBindingAdapterTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.ViewAdapterTestBinding;
+import android.databinding.testapp.vo.ViewBindingObject;
+
+import android.content.res.ColorStateList;
+import android.os.Build;
+import android.test.UiThreadTest;
+import android.view.View;
+
+public class ViewBindingAdapterTest extends BindingAdapterTestBase<ViewAdapterTestBinding, ViewBindingObject> {
+
+ public ViewBindingAdapterTest() {
+ super(ViewAdapterTestBinding.class, ViewBindingObject.class, R.layout.view_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public void testPadding() throws Throwable {
+ View view = mBinder.padding;
+ assertEquals(mBindingObject.getPadding(), view.getPaddingBottom());
+ assertEquals(mBindingObject.getPadding(), view.getPaddingTop());
+ assertEquals(mBindingObject.getPadding(), view.getPaddingRight());
+ assertEquals(mBindingObject.getPadding(), view.getPaddingLeft());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getPadding(), view.getPaddingBottom());
+ assertEquals(mBindingObject.getPadding(), view.getPaddingTop());
+ assertEquals(mBindingObject.getPadding(), view.getPaddingRight());
+ assertEquals(mBindingObject.getPadding(), view.getPaddingLeft());
+ }
+
+ public void testPaddingLeftRight() throws Throwable {
+ View view = mBinder.paddingLeftRight;
+ assertEquals(mBindingObject.getPaddingLeft(), view.getPaddingLeft());
+ assertEquals(mBindingObject.getPaddingRight(), view.getPaddingRight());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getPaddingLeft(), view.getPaddingLeft());
+ assertEquals(mBindingObject.getPaddingRight(), view.getPaddingRight());
+ }
+
+ public void testPaddingStartEnd() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ View view = mBinder.paddingStartEnd;
+ assertEquals(mBindingObject.getPaddingStart(), view.getPaddingStart());
+ assertEquals(mBindingObject.getPaddingEnd(), view.getPaddingEnd());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getPaddingStart(), view.getPaddingStart());
+ assertEquals(mBindingObject.getPaddingEnd(), view.getPaddingEnd());
+ }
+ }
+
+ public void testPaddingTopBottom() throws Throwable {
+ View view = mBinder.paddingTopBottom;
+ assertEquals(mBindingObject.getPaddingTop(), view.getPaddingTop());
+ assertEquals(mBindingObject.getPaddingBottom(), view.getPaddingBottom());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getPaddingTop(), view.getPaddingTop());
+ assertEquals(mBindingObject.getPaddingBottom(), view.getPaddingBottom());
+ }
+
+ public void testBackgroundTint() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ View view = mBinder.backgroundTint;
+ assertNotNull(view.getBackgroundTintList());
+ ColorStateList colorStateList = view.getBackgroundTintList();
+ assertEquals(mBindingObject.getBackgroundTint(), colorStateList.getDefaultColor());
+
+ changeValues();
+
+ assertNotNull(view.getBackgroundTintList());
+ colorStateList = view.getBackgroundTintList();
+ assertEquals(mBindingObject.getBackgroundTint(), colorStateList.getDefaultColor());
+ }
+ }
+
+ public void testFadeScrollbars() throws Throwable {
+ View view = mBinder.fadeScrollbars;
+ assertEquals(mBindingObject.getFadeScrollbars(), view.isScrollbarFadingEnabled());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getFadeScrollbars(), view.isScrollbarFadingEnabled());
+ }
+
+ public void testNextFocus() throws Throwable {
+ View view = mBinder.nextFocus;
+
+ assertEquals(mBindingObject.getNextFocusDown(), view.getNextFocusDownId());
+ assertEquals(mBindingObject.getNextFocusUp(), view.getNextFocusUpId());
+ assertEquals(mBindingObject.getNextFocusLeft(), view.getNextFocusLeftId());
+ assertEquals(mBindingObject.getNextFocusRight(), view.getNextFocusRightId());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ assertEquals(mBindingObject.getNextFocusForward(), view.getNextFocusForwardId());
+ }
+
+ changeValues();
+
+ assertEquals(mBindingObject.getNextFocusDown(), view.getNextFocusDownId());
+ assertEquals(mBindingObject.getNextFocusUp(), view.getNextFocusUpId());
+ assertEquals(mBindingObject.getNextFocusLeft(), view.getNextFocusLeftId());
+ assertEquals(mBindingObject.getNextFocusRight(), view.getNextFocusRightId());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ assertEquals(mBindingObject.getNextFocusForward(), view.getNextFocusForwardId());
+ }
+ }
+
+ public void testRequiresFadingEdge() throws Throwable {
+ View view = mBinder.requiresFadingEdge;
+
+ assertTrue(view.isVerticalFadingEdgeEnabled());
+ assertFalse(view.isHorizontalFadingEdgeEnabled());
+
+ changeValues();
+
+ assertFalse(view.isVerticalFadingEdgeEnabled());
+ assertTrue(view.isHorizontalFadingEdgeEnabled());
+ }
+
+ public void testScrollbar() throws Throwable {
+ View view = mBinder.scrollbar;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ assertEquals(mBindingObject.getScrollbarDefaultDelayBeforeFade(),
+ view.getScrollBarDefaultDelayBeforeFade());
+ assertEquals(mBindingObject.getScrollbarFadeDuration(), view.getScrollBarFadeDuration());
+ assertEquals(mBindingObject.getScrollbarSize(), view.getScrollBarSize());
+ }
+ assertEquals(mBindingObject.getScrollbarStyle(), view.getScrollBarStyle());
+
+ changeValues();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ assertEquals(mBindingObject.getScrollbarDefaultDelayBeforeFade(),
+ view.getScrollBarDefaultDelayBeforeFade());
+ assertEquals(mBindingObject.getScrollbarFadeDuration(), view.getScrollBarFadeDuration());
+ assertEquals(mBindingObject.getScrollbarSize(), view.getScrollBarSize());
+ }
+ assertEquals(mBindingObject.getScrollbarStyle(), view.getScrollBarStyle());
+ }
+
+ public void testTransformPivot() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ View view = mBinder.transformPivot;
+
+ assertEquals(mBindingObject.getTransformPivotX(), view.getPivotX());
+ assertEquals(mBindingObject.getTransformPivotY(), view.getPivotY());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getTransformPivotX(), view.getPivotX());
+ assertEquals(mBindingObject.getTransformPivotY(), view.getPivotY());
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewGroupBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewGroupBindingAdapterTest.java
new file mode 100644
index 0000000..981495d
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewGroupBindingAdapterTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.ViewGroupAdapterTestBinding;
+import android.databinding.testapp.vo.ViewGroupBindingObject;
+
+import android.os.Build;
+import android.view.ViewGroup;
+
+public class ViewGroupBindingAdapterTest
+ extends BindingAdapterTestBase<ViewGroupAdapterTestBinding, ViewGroupBindingObject> {
+
+ ViewGroup mView;
+
+ public ViewGroupBindingAdapterTest() {
+ super(ViewGroupAdapterTestBinding.class, ViewGroupBindingObject.class,
+ R.layout.view_group_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view;
+ }
+
+ public void testDrawnWithCache() throws Throwable {
+ assertEquals(mBindingObject.isAlwaysDrawnWithCache(),
+ mView.isAlwaysDrawnWithCacheEnabled());
+
+ changeValues();
+
+ assertEquals(mBindingObject.isAlwaysDrawnWithCache(),
+ mView.isAlwaysDrawnWithCacheEnabled());
+ }
+
+ public void testAnimationCache() throws Throwable {
+ assertEquals(mBindingObject.isAnimationCache(), mView.isAnimationCacheEnabled());
+
+ changeValues();
+
+ assertEquals(mBindingObject.isAnimationCache(), mView.isAnimationCacheEnabled());
+ }
+
+ public void testSplitMotionEvents() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ assertEquals(mBindingObject.isSplitMotionEvents(),
+ mView.isMotionEventSplittingEnabled());
+
+ changeValues();
+
+ assertEquals(mBindingObject.isSplitMotionEvents(),
+ mView.isMotionEventSplittingEnabled());
+ }
+ }
+
+ public void testAnimateLayoutChanges() throws Throwable {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ assertEquals(mBindingObject.isAnimateLayoutChanges(),
+ mView.getLayoutTransition() != null);
+
+ changeValues();
+
+ assertEquals(mBindingObject.isAnimateLayoutChanges(),
+ mView.getLayoutTransition() != null);
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubBindingAdapterTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubBindingAdapterTest.java
new file mode 100644
index 0000000..87cb5b2
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubBindingAdapterTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.ViewStubAdapterTestBinding;
+import android.databinding.testapp.vo.ViewStubBindingObject;
+
+import android.view.ViewStub;
+
+public class ViewStubBindingAdapterTest
+ extends BindingAdapterTestBase<ViewStubAdapterTestBinding, ViewStubBindingObject> {
+
+ ViewStub mView;
+
+ public ViewStubBindingAdapterTest() {
+ super(ViewStubAdapterTestBinding.class, ViewStubBindingObject.class,
+ R.layout.view_stub_adapter_test);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mView = mBinder.view.getViewStub();
+ }
+
+ public void testLayout() throws Throwable {
+ assertEquals(mBindingObject.getLayout(), mView.getLayoutResource());
+
+ changeValues();
+
+ assertEquals(mBindingObject.getLayout(), mView.getLayoutResource());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubTest.java
new file mode 100644
index 0000000..a78bda3
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/ViewStubTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.ViewStubBinding;
+import android.databinding.testapp.databinding.ViewStubContentsBinding;
+import android.databinding.ViewStubProxy;
+import android.support.v4.util.ArrayMap;
+import android.test.UiThreadTest;
+import android.view.View;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class ViewStubTest extends BaseDataBinderTest<ViewStubBinding> {
+
+ public ViewStubTest() {
+ super(ViewStubBinding.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mBinder.setViewStubVisibility(View.GONE);
+ mBinder.setFirstName("Hello");
+ mBinder.setLastName("World");
+ try {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mBinder.executePendingBindings();
+ }
+ });
+ } catch (Exception e) {
+ throw e;
+ } catch (Throwable t) {
+ throw new Exception(t);
+ }
+ }
+
+ @UiThreadTest
+ public void testInflation() throws Throwable {
+ ViewStubProxy viewStubProxy = mBinder.viewStub;
+ assertFalse(viewStubProxy.isInflated());
+ assertNull(viewStubProxy.getBinding());
+ assertNotNull(viewStubProxy.getViewStub());
+ assertNull(mBinder.getRoot().findViewById(R.id.firstNameContents));
+ assertNull(mBinder.getRoot().findViewById(R.id.lastNameContents));
+ mBinder.setViewStubVisibility(View.VISIBLE);
+ mBinder.executePendingBindings();
+ assertTrue(viewStubProxy.isInflated());
+ assertNotNull(viewStubProxy.getBinding());
+ assertNull(viewStubProxy.getViewStub());
+ ViewStubContentsBinding contentsBinding = (ViewStubContentsBinding)
+ viewStubProxy.getBinding();
+ assertNotNull(contentsBinding.firstNameContents);
+ assertNotNull(contentsBinding.lastNameContents);
+ assertEquals("Hello", contentsBinding.firstNameContents.getText().toString());
+ assertEquals("World", contentsBinding.lastNameContents.getText().toString());
+ }
+
+ @UiThreadTest
+ public void testChangeValues() throws Throwable {
+ ViewStubProxy viewStubProxy = mBinder.viewStub;
+ mBinder.setViewStubVisibility(View.VISIBLE);
+ mBinder.executePendingBindings();
+ ViewStubContentsBinding contentsBinding = (ViewStubContentsBinding)
+ viewStubProxy.getBinding();
+ assertEquals("Hello", contentsBinding.firstNameContents.getText().toString());
+ mBinder.setFirstName("Goodbye");
+ mBinder.executePendingBindings();
+ assertEquals("Goodbye", contentsBinding.firstNameContents.getText().toString());
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/multiconfig/LandscapeConfigTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/multiconfig/LandscapeConfigTest.java
new file mode 100644
index 0000000..7f38f3b
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/multiconfig/LandscapeConfigTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp.multiconfig;
+
+import android.databinding.ViewDataBinding;
+import android.databinding.testapp.BaseLandDataBinderTest;
+import android.databinding.testapp.R;
+import android.databinding.testapp.databinding.BasicBindingBinding;
+import android.databinding.testapp.databinding.ConditionalBindingBinding;
+import android.databinding.testapp.databinding.IncludedLayoutBinding;
+import android.databinding.testapp.databinding.MultiResLayoutBinding;
+import android.databinding.testapp.vo.NotBindableVo;
+
+import android.content.pm.ActivityInfo;
+import android.view.View;
+import android.widget.TextView;
+
+public class LandscapeConfigTest extends BaseLandDataBinderTest<MultiResLayoutBinding> {
+
+ public LandscapeConfigTest() {
+ super(MultiResLayoutBinding.class, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ }
+
+ public void testSharedViewIdAndVariableInheritance()
+ throws InterruptedException, NoSuchMethodException, NoSuchFieldException {
+ assertEquals("MultiResLayoutBindingLandImpl", mBinder.getClass().getSimpleName());
+ assertPublicField(TextView.class, "objectInLandTextView");
+ assertPublicField(TextView.class, "objectInDefaultTextView");
+ assertPublicField(View.class, "objectInDefaultTextView2");
+
+ assertField(NotBindableVo.class, "mObjectInLand");
+ assertField(NotBindableVo.class, "mObjectInDefault");
+
+ // includes
+ assertPublicField(ViewDataBinding.class, "includedLayoutConflict");
+ assertPublicField(BasicBindingBinding.class, "includedLayoutShared");
+ assertPublicField(ConditionalBindingBinding.class, "includedLayoutPort");
+ assertPublicField(ConditionalBindingBinding.class, "includedLayoutLand");
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/multiconfig/PortraitConfigTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/multiconfig/PortraitConfigTest.java
new file mode 100644
index 0000000..a4e4f2f
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/multiconfig/PortraitConfigTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp.multiconfig;
+
+import android.databinding.ViewDataBinding;
+import android.databinding.testapp.BaseDataBinderTest;
+import android.databinding.testapp.databinding.BasicBindingBinding;
+import android.databinding.testapp.databinding.ConditionalBindingBinding;
+import android.databinding.testapp.databinding.IncludedLayoutBinding;
+import android.databinding.testapp.databinding.MultiResLayoutBinding;
+import android.databinding.testapp.vo.NotBindableVo;
+
+import android.content.pm.ActivityInfo;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+
+public class PortraitConfigTest extends BaseDataBinderTest<MultiResLayoutBinding> {
+ public PortraitConfigTest() {
+ super(MultiResLayoutBinding.class, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+
+ public void testSharedViewIdAndVariableInheritance()
+ throws InterruptedException, NoSuchMethodException, NoSuchFieldException {
+ assertEquals("MultiResLayoutBindingImpl", mBinder.getClass().getSimpleName());
+ assertPublicField(TextView.class, "objectInLandTextView");
+ assertPublicField(TextView.class, "objectInDefaultTextView");
+ assertPublicField(View.class, "objectInDefaultTextView2");
+
+ assertField(NotBindableVo.class, "mObjectInDefault");
+
+ // includes
+ assertPublicField(ViewDataBinding.class, "includedLayoutConflict");
+ assertPublicField(BasicBindingBinding.class, "includedLayoutShared");
+ assertPublicField(ConditionalBindingBinding.class, "includedLayoutPort");
+ assertPublicField(ConditionalBindingBinding.class, "includedLayoutLand");
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/AndroidManifest.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c6f719e
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.databinding.testapp">
+
+ <application android:allowBackup="true"
+ android:label="@string/app_name"
+ android:icon="@drawable/ic_launcher"
+ >
+ <activity android:name=".TestActivity"
+ android:screenOrientation="portrait"/>
+ </application>
+
+</manifest>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/TestActivity.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/TestActivity.java
new file mode 100644
index 0000000..d5f95e6
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/TestActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class TestActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/AbsListViewBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/AbsListViewBindingObject.java
new file mode 100644
index 0000000..24bc067
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/AbsListViewBindingObject.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+import android.graphics.drawable.ColorDrawable;
+
+public class AbsListViewBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private ColorDrawable mListSelector = new ColorDrawable(0xFFFF0000);
+ @Bindable
+ private boolean mScrollingCache;
+ @Bindable
+ private boolean mSmoothScrollbar;
+
+ public ColorDrawable getListSelector() {
+ return mListSelector;
+ }
+
+ public boolean isScrollingCache() {
+ return mScrollingCache;
+ }
+
+ public boolean isSmoothScrollbar() {
+ return mSmoothScrollbar;
+ }
+
+ public void changeValues() {
+ mListSelector = new ColorDrawable(0xFFFFFFFF);
+ mScrollingCache = true;
+ mSmoothScrollbar = true;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/AbsSeekBarBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/AbsSeekBarBindingObject.java
new file mode 100644
index 0000000..bc8c3cb
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/AbsSeekBarBindingObject.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+
+public class AbsSeekBarBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private int mThumbTint = 0xFFFF0000;
+
+ public int getThumbTint() {
+ return mThumbTint;
+ }
+
+ public void changeValues() {
+ mThumbTint = 0xFF00FF00;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/AbsSpinnerBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/AbsSpinnerBindingObject.java
new file mode 100644
index 0000000..05d7ed7
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/AbsSpinnerBindingObject.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+
+public class AbsSpinnerBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private CharSequence[] mEntries = {
+ "hello",
+ "world",
+ };
+
+ private static final CharSequence[] CHANGED_VALUES = {
+ "goodbye",
+ "cruel",
+ "world"
+ };
+
+ public CharSequence[] getEntries() {
+ return mEntries;
+ }
+
+ public void changeValues() {
+ mEntries = CHANGED_VALUES;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/AutoCompleteTextViewBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/AutoCompleteTextViewBindingObject.java
new file mode 100644
index 0000000..eef46d6
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/AutoCompleteTextViewBindingObject.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+
+public class AutoCompleteTextViewBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private int mPopupBackground;
+
+ @Bindable
+ private int mCompletionThreshold = 1;
+
+ public int getCompletionThreshold() {
+ return mCompletionThreshold;
+ }
+
+ public int getPopupBackground() {
+ return mPopupBackground;
+ }
+
+ public void changeValues() {
+ mPopupBackground = 0xFF23456;
+ mCompletionThreshold = 5;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BasicObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BasicObject.java
new file mode 100644
index 0000000..450f7fb
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BasicObject.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp.vo;
+import android.databinding.BaseObservable;
+import android.databinding.Bindable;
+import android.databinding.testapp.BR;
+
+public class BasicObject extends BaseObservable {
+ @Bindable
+ private String mField1;
+ @Bindable
+ private String mField2;
+
+ public String getField1() {
+ return mField1;
+ }
+
+ public void setField1(String field1) {
+ this.mField1 = field1;
+ notifyPropertyChanged(BR.field1);
+ }
+
+ public String getField2() {
+ return mField2;
+ }
+
+ public void setField2(String field2) {
+ this.mField2 = field2;
+ notifyPropertyChanged(BR.field1);
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BindableTestObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BindableTestObject.java
new file mode 100644
index 0000000..d297f8a
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BindableTestObject.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+
+public class BindableTestObject {
+ @Bindable
+ public int bindableField1;
+
+ @Bindable
+ private int bindableField2;
+
+ private int bindableField3;
+
+ @Bindable
+ public int m_bindableField4;
+
+ @Bindable
+ public int mbindableField5;
+
+ @Bindable
+ public int _bindableField6;
+
+ @Bindable
+ public int _BindableField7;
+
+ @Bindable
+ public int mBindableField8;
+
+ public int getBindableField2() {
+ return bindableField2;
+ }
+
+ @Bindable
+ public int getBindableField3() {
+ return bindableField3;
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BindingAdapterBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BindingAdapterBindingObject.java
new file mode 100644
index 0000000..404e104
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BindingAdapterBindingObject.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.BaseObservable;
+
+public abstract class BindingAdapterBindingObject extends BaseObservable {
+
+ public abstract void changeValues();
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/CheckedTextViewBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/CheckedTextViewBindingObject.java
new file mode 100644
index 0000000..75eba50
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/CheckedTextViewBindingObject.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+import android.graphics.drawable.ColorDrawable;
+
+public class CheckedTextViewBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private ColorDrawable mCheckMark = new ColorDrawable(0xFF123456);
+
+ @Bindable
+ private int mCheckMarkTint = 0xDead_Beef;
+
+ public ColorDrawable getCheckMark() {
+ return mCheckMark;
+ }
+
+ public int getCheckMarkTint() {
+ return mCheckMarkTint;
+ }
+
+ public void changeValues() {
+ mCheckMark = new ColorDrawable(0xFF111111);
+ mCheckMarkTint = 0xFF222222;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/CompoundButtonBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/CompoundButtonBindingObject.java
new file mode 100644
index 0000000..bb13201
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/CompoundButtonBindingObject.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+
+public class CompoundButtonBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private int mButtonTint;
+
+ public int getButtonTint() {
+ return mButtonTint;
+ }
+
+ public void changeValues() {
+ mButtonTint = 0xFF111111;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/FindMethodBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/FindMethodBindingObject.java
new file mode 100644
index 0000000..2281e22
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/FindMethodBindingObject.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.util.ArrayMap;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class FindMethodBindingObject extends FindMethodBindingObjectBase {
+ public String method() { return "no arg"; }
+
+ public String method(int i) { return String.valueOf(i); }
+
+ public String method(float f) { return String.valueOf(f); }
+
+ public String method(String value) { return value; }
+
+ public static String staticMethod() { return "world"; }
+
+ public static Foo foo = new Foo();
+
+ public static Bar<String> bar = new Bar<>();
+
+ public float confusingParam(int i) { return i; }
+ public String confusingParam(Object o) { return o.toString(); }
+
+ public int confusingPrimitive(int i) { return i; }
+ public String confusingPrimitive(Integer i) { return i.toString(); }
+
+ public float confusingInheritance(Object o) { return 0; }
+ public String confusingInheritance(String s) { return s; }
+ public int confusingInheritance(Integer i) { return i; }
+
+ public int confusingTypeArgs(List<String> s) { return 0; }
+ public String confusingTypeArgs(Map<String, String> s) { return "yay"; }
+
+ public ArrayMap<String, String> getMap() { return null; }
+
+ public List getList() {
+ ArrayList<String> vals = new ArrayList<>();
+ vals.add("hello");
+ return vals;
+ }
+
+ public static class Foo {
+ public final String bar = "hello world";
+ }
+
+ public static class Bar<T> {
+ public T method(T value) { return value; }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/FindMethodBindingObjectBase.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/FindMethodBindingObjectBase.java
new file mode 100644
index 0000000..8678e1e
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/FindMethodBindingObjectBase.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+public class FindMethodBindingObjectBase extends BindingAdapterBindingObject {
+ public String inheritedMethod() {
+ return "base";
+ }
+
+ public String inheritedMethod(int i) {
+ return "base " + i;
+ }
+
+ @Override
+ public void changeValues() {
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/FrameLayoutBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/FrameLayoutBindingObject.java
new file mode 100644
index 0000000..a0fa6b7
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/FrameLayoutBindingObject.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+
+public class FrameLayoutBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private int foregroundTint;
+
+ public int getForegroundTint() {
+ return foregroundTint;
+ }
+
+ public void changeValues() {
+ foregroundTint = 0xFF111111;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ImageViewBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ImageViewBindingObject.java
new file mode 100644
index 0000000..926617e
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ImageViewBindingObject.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+
+public class ImageViewBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private int mTint;
+
+ @Bindable
+ private Drawable mSrc;
+
+ @Bindable
+ private PorterDuff.Mode mTintMode = PorterDuff.Mode.DARKEN;
+
+ public int getTint() {
+ return mTint;
+ }
+
+ public Drawable getSrc() {
+ return mSrc;
+ }
+
+ public PorterDuff.Mode getTintMode() {
+ return mTintMode;
+ }
+
+ public void changeValues() {
+ mTint = 0xFF111111;
+ mSrc = new ColorDrawable(0xFF00FF00);
+ mTintMode = PorterDuff.Mode.LIGHTEN;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/LinearLayoutBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/LinearLayoutBindingObject.java
new file mode 100644
index 0000000..f9e07c3
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/LinearLayoutBindingObject.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+
+public class LinearLayoutBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private int mDivider;
+
+ @Bindable
+ private boolean mMeasureWithLargestChild;
+
+ public int getDivider() {
+ return mDivider;
+ }
+
+ public boolean isMeasureWithLargestChild() {
+ return mMeasureWithLargestChild;
+ }
+
+ public void changeValues() {
+ mDivider = 0xFF111111;
+ mMeasureWithLargestChild = true;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/NotBindableVo.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/NotBindableVo.java
new file mode 100644
index 0000000..64d1a48
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/NotBindableVo.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp.vo;
+
+public class NotBindableVo {
+ private int mIntValue;
+ private int mIntValueGetCount;
+ private boolean mBoolValue;
+ private int mBoolValueGetCount;
+ private String mStringValue;
+ private int mStringValueGetCount;
+ private final String mFinalString = "this has final content";
+ public final int publicField = 3;
+
+ public NotBindableVo() {
+ }
+
+ public NotBindableVo(int intValue) {
+ this.mIntValue = intValue;
+ }
+
+ public NotBindableVo(String stringValue) {
+ this.mStringValue = stringValue;
+ }
+
+ public NotBindableVo(int intValue, String stringValue) {
+ this.mIntValue = intValue;
+ this.mStringValue = stringValue;
+ }
+
+ public int getIntValue() {
+ mIntValueGetCount ++;
+ return mIntValue;
+ }
+
+ public String getFinalString() {
+ return mFinalString;
+ }
+
+ public void setIntValue(int intValue) {
+ this.mIntValue = intValue;
+ }
+
+ public String getStringValue() {
+ mStringValueGetCount ++;
+ return mStringValue;
+ }
+
+ public void setStringValue(String stringValue) {
+ this.mStringValue = stringValue;
+ }
+
+ public String mergeStringFields(NotBindableVo other) {
+ return mStringValue + (other == null ? "" : other.mStringValue);
+ }
+
+ public boolean getBoolValue() {
+ mBoolValueGetCount ++;
+ return mBoolValue;
+ }
+
+ public void setBoolValue(boolean boolValue) {
+ mBoolValue = boolValue;
+ }
+
+ public int getIntValueGetCount() {
+ return mIntValueGetCount;
+ }
+
+ public int getBoolValueGetCount() {
+ return mBoolValueGetCount;
+ }
+
+ public int getStringValueGetCount() {
+ return mStringValueGetCount;
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ObservableFieldBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ObservableFieldBindingObject.java
new file mode 100644
index 0000000..87127fe
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ObservableFieldBindingObject.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.ObservableBoolean;
+import android.databinding.ObservableByte;
+import android.databinding.ObservableChar;
+import android.databinding.ObservableDouble;
+import android.databinding.ObservableField;
+import android.databinding.ObservableFloat;
+import android.databinding.ObservableInt;
+import android.databinding.ObservableLong;
+import android.databinding.ObservableShort;
+
+public class ObservableFieldBindingObject {
+ public final ObservableBoolean bField = new ObservableBoolean();
+ public final ObservableByte tField = new ObservableByte();
+ public final ObservableShort sField = new ObservableShort();
+ public final ObservableChar cField = new ObservableChar();
+ public final ObservableInt iField = new ObservableInt();
+ public final ObservableLong lField = new ObservableLong();
+ public final ObservableFloat fField = new ObservableFloat();
+ public final ObservableDouble dField = new ObservableDouble();
+ public final ObservableField<String> oField = new ObservableField<>();
+
+ public ObservableFieldBindingObject() {
+ oField.set("Hello");
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ObservableWithNotBindableFieldObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ObservableWithNotBindableFieldObject.java
new file mode 100644
index 0000000..ba33539
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ObservableWithNotBindableFieldObject.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp.vo;
+
+import android.databinding.BaseObservable;
+
+public class ObservableWithNotBindableFieldObject extends BaseObservable {
+ private String data;
+ public void update(String data) {
+ this.data = data;
+ notifyChange();
+ }
+
+ public String getData() {
+ return data;
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ProgressBarBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ProgressBarBindingObject.java
new file mode 100644
index 0000000..8a18aba
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ProgressBarBindingObject.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+
+public class ProgressBarBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private int mIndeterminateTint;
+
+ @Bindable
+ private int mProgressTint;
+
+ @Bindable
+ private int mSecondaryProgressTint;
+
+ public int getIndeterminateTint() {
+ return mIndeterminateTint;
+ }
+
+ public int getProgressTint() {
+ return mProgressTint;
+ }
+
+ public int getSecondaryProgressTint() {
+ return mSecondaryProgressTint;
+ }
+
+ public void changeValues() {
+ mIndeterminateTint = 0xFF111111;
+ mProgressTint = 0xFF222222;
+ mSecondaryProgressTint = 0xFF333333;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/PublicFinalTestVo.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/PublicFinalTestVo.java
new file mode 100644
index 0000000..03135e3
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/PublicFinalTestVo.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp.vo;
+
+public class PublicFinalTestVo {
+ public final int myField;
+ public PublicFinalTestVo(int field) {
+ myField = field;
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/PublicFinalWithObservableTestVo.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/PublicFinalWithObservableTestVo.java
new file mode 100644
index 0000000..8d6f431
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/PublicFinalWithObservableTestVo.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.testapp.vo;
+
+import android.databinding.BaseObservable;
+import android.databinding.Bindable;
+import android.databinding.testapp.BR;
+import android.databinding.testapp.R;
+
+public class PublicFinalWithObservableTestVo {
+ public final int myField;
+ public final MyVo myFinalVo = new MyVo();
+
+ public PublicFinalWithObservableTestVo(int field) {
+ myField = field;
+ }
+
+ public static class MyVo extends BaseObservable {
+ @Bindable
+ private int val = R.string.app_name;
+
+ public int getVal() {
+ return val;
+ }
+
+ public void setVal(int val) {
+ this.val = val;
+ notifyPropertyChanged(BR.val);
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/RadioGroupBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/RadioGroupBindingObject.java
new file mode 100644
index 0000000..ceacd20
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/RadioGroupBindingObject.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+import android.databinding.testapp.R;
+
+public class RadioGroupBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private int mCheckedButton = R.id.choiceOne;
+
+ public int getCheckedButton() {
+ return mCheckedButton;
+ }
+
+ public void changeValues() {
+ mCheckedButton = R.id.choiceTwo;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/SpinnerBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/SpinnerBindingObject.java
new file mode 100644
index 0000000..c2ac72f
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/SpinnerBindingObject.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+
+public class SpinnerBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private int mPopupBackground = 0xFF123456;
+
+ public int getPopupBackground() {
+ return mPopupBackground;
+ }
+
+ public void changeValues() {
+ mPopupBackground = 0xFF111111;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/SwitchBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/SwitchBindingObject.java
new file mode 100644
index 0000000..86ea227
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/SwitchBindingObject.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+
+public class SwitchBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private int mThumb;
+ @Bindable
+ private int mTrack;
+
+ public int getThumb() {
+ return mThumb;
+ }
+
+ public int getTrack() {
+ return mTrack;
+ }
+
+ public void changeValues() {
+ mThumb = 0xFF111111;
+ mTrack = 0xFF333333;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/TabWidgetBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/TabWidgetBindingObject.java
new file mode 100644
index 0000000..2ce8681
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/TabWidgetBindingObject.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+import android.graphics.drawable.ColorDrawable;
+
+public class TabWidgetBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private ColorDrawable mDivider = new ColorDrawable(0xFF0000FF);
+ @Bindable
+ private boolean mTabStripEnabled;
+ @Bindable
+ private ColorDrawable mTabStripLeft = new ColorDrawable(0xFF00FF00);
+ @Bindable
+ private ColorDrawable mTabStripRight = new ColorDrawable(0xFFFF0000);
+
+ public ColorDrawable getDivider() {
+ return mDivider;
+ }
+
+ public ColorDrawable getTabStripLeft() {
+ return mTabStripLeft;
+ }
+
+ public ColorDrawable getTabStripRight() {
+ return mTabStripRight;
+ }
+
+ public boolean isTabStripEnabled() {
+ return mTabStripEnabled;
+ }
+
+ public void changeValues() {
+ mDivider = new ColorDrawable(0xFF111111);
+ mTabStripEnabled = true;
+ mTabStripLeft = new ColorDrawable(0xFF222222);
+ mTabStripRight = new ColorDrawable(0xFF333333);
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/TableLayoutBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/TableLayoutBindingObject.java
new file mode 100644
index 0000000..34f7e8d
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/TableLayoutBindingObject.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+
+public class TableLayoutBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private String mCollapseColumns = "1";
+ @Bindable
+ private String mShrinkColumns = "1";
+ @Bindable
+ private String mStretchColumns = "1";
+ @Bindable
+ private int mDivider = 0xFF112233;
+
+ public String getCollapseColumns() {
+ return mCollapseColumns;
+ }
+
+ public String getShrinkColumns() {
+ return mShrinkColumns;
+ }
+
+ public String getStretchColumns() {
+ return mStretchColumns;
+ }
+
+ public int getDivider() {
+ return mDivider;
+ }
+
+ public void changeValues() {
+ mCollapseColumns = "";
+ mShrinkColumns = "1,0";
+ mStretchColumns = "*";
+ mDivider = 0xFF445566;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/TextViewBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/TextViewBindingObject.java
new file mode 100644
index 0000000..b1d04b6
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/TextViewBindingObject.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+import android.databinding.adapters.TextViewBindingAdapter;
+import android.databinding.testapp.BR;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.method.KeyListener;
+import android.text.method.TextKeyListener;
+import android.text.util.Linkify;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.TextView;
+
+public class TextViewBindingObject extends BindingAdapterBindingObject {
+
+ @Bindable
+ private int mAutoLink = Linkify.WEB_URLS;
+
+ @Bindable
+ private int mDrawablePadding;
+
+ @Bindable
+ private int mInputType = InputType.TYPE_CLASS_PHONE;
+
+ @Bindable
+ private boolean mScrollHorizontally;
+
+ @Bindable
+ private boolean mTextAllCaps;
+
+ @Bindable
+ private int mTextColorHighlight;
+
+ @Bindable
+ private int mTextColorHint;
+
+ @Bindable
+ private int mTextColorLink;
+
+ @Bindable
+ private boolean mAutoText;
+
+ @Bindable
+ private TextKeyListener.Capitalize mCapitalize = TextKeyListener.Capitalize.NONE;
+
+ @Bindable
+ private TextView.BufferType mBufferType = TextView.BufferType.NORMAL;
+
+ @Bindable
+ private String mDigits = "abcdefg";
+
+ @Bindable
+ private int mNumeric = TextViewBindingAdapter.DECIMAL;
+
+ @Bindable
+ private boolean mPhoneNumber;
+
+ @Bindable
+ private int mDrawableBottom;
+
+ @Bindable
+ private int mDrawableTop;
+
+ @Bindable
+ private int mDrawableLeft;
+
+ @Bindable
+ private int mDrawableRight;
+
+ @Bindable
+ private int mDrawableStart;
+
+ @Bindable
+ private int mDrawableEnd;
+
+ @Bindable
+ private String mImeActionLabel;
+
+ @Bindable
+ private int mImeActionId;
+
+ @Bindable
+ private String mInputMethod
+ = "android.databinding.testapp.vo.TextViewBindingObject$KeyListener1";
+
+ @Bindable
+ private float mLineSpacingExtra;
+
+ @Bindable
+ private float mLineSpacingMultiplier;
+
+ @Bindable
+ private int mMaxLength;
+
+ @Bindable
+ private int mShadowColor;
+
+ @Bindable
+ private float mShadowDx;
+
+ @Bindable
+ private float mShadowDy;
+
+ @Bindable
+ private float mShadowRadius;
+
+ @Bindable
+ private float mTextSize = 10f;
+
+ public TextView.BufferType getBufferType() {
+ return mBufferType;
+ }
+
+ public float getLineSpacingExtra() {
+ return mLineSpacingExtra;
+ }
+
+ public float getLineSpacingMultiplier() {
+ return mLineSpacingMultiplier;
+ }
+
+ public float getShadowDx() {
+ return mShadowDx;
+ }
+
+ public float getShadowDy() {
+ return mShadowDy;
+ }
+
+ public float getShadowRadius() {
+ return mShadowRadius;
+ }
+
+ public float getTextSize() {
+ return mTextSize;
+ }
+
+ public int getAutoLink() {
+ return mAutoLink;
+ }
+
+ public int getDrawableBottom() {
+ return mDrawableBottom;
+ }
+
+ public int getDrawableEnd() {
+ return mDrawableEnd;
+ }
+
+ public int getDrawableLeft() {
+ return mDrawableLeft;
+ }
+
+ public int getDrawablePadding() {
+ return mDrawablePadding;
+ }
+
+ public int getDrawableRight() {
+ return mDrawableRight;
+ }
+
+ public int getDrawableStart() {
+ return mDrawableStart;
+ }
+
+ public int getDrawableTop() {
+ return mDrawableTop;
+ }
+
+ public int getImeActionId() {
+ return mImeActionId;
+ }
+
+ public int getInputType() {
+ return mInputType;
+ }
+
+ public int getMaxLength() {
+ return mMaxLength;
+ }
+
+ public int getNumeric() {
+ return mNumeric;
+ }
+
+ public int getShadowColor() {
+ return mShadowColor;
+ }
+
+ public int getTextColorHighlight() {
+ return mTextColorHighlight;
+ }
+
+ public int getTextColorHint() {
+ return mTextColorHint;
+ }
+
+ public int getTextColorLink() {
+ return mTextColorLink;
+ }
+
+ public String getDigits() {
+ return mDigits;
+ }
+
+ public String getImeActionLabel() {
+ return mImeActionLabel;
+ }
+
+ public String getInputMethod() {
+ return mInputMethod;
+ }
+
+ public boolean isAutoText() {
+ return mAutoText;
+ }
+
+ public TextKeyListener.Capitalize getCapitalize() {
+ return mCapitalize;
+ }
+
+ public void setCapitalize(TextKeyListener.Capitalize capitalize) {
+ mCapitalize = capitalize;
+ notifyPropertyChanged(BR.capitalize);
+ }
+
+ public boolean isPhoneNumber() {
+ return mPhoneNumber;
+ }
+
+ public boolean isScrollHorizontally() {
+ return mScrollHorizontally;
+ }
+
+ public boolean isTextAllCaps() {
+ return mTextAllCaps;
+ }
+
+ public void changeValues() {
+ mAutoLink = Linkify.EMAIL_ADDRESSES;
+ mDrawablePadding = 10;
+ mInputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS;
+ mScrollHorizontally = true;
+ mTextAllCaps = true;
+ mTextColorHighlight = 0xFF00FF00;
+ mTextColorHint = 0xFFFF0000;
+ mTextColorLink = 0xFF0000FF;
+ mAutoText = true;
+ mCapitalize = TextKeyListener.Capitalize.SENTENCES;
+ mBufferType = TextView.BufferType.SPANNABLE;
+ mDigits = "hijklmno";
+ mNumeric = TextViewBindingAdapter.SIGNED;
+ mPhoneNumber = true;
+ mDrawableBottom = 0xFF880088;
+ mDrawableTop = 0xFF111111;
+ mDrawableLeft = 0xFF222222;
+ mDrawableRight = 0xFF333333;
+ mDrawableStart = 0xFF444444;
+ mDrawableEnd = 0xFF555555;
+ mImeActionLabel = "Hello World";
+ mImeActionId = 3;
+ mInputMethod = "android.databinding.testapp.vo.TextViewBindingObject$KeyListener2";
+ mLineSpacingExtra = 2;
+ mLineSpacingMultiplier = 3;
+ mMaxLength = 100;
+ mShadowColor = 0xFF666666;
+ mShadowDx = 2;
+ mShadowDy = 3;
+ mShadowRadius = 4;
+ mTextSize = 20f;
+ notifyChange();
+ }
+
+ public static class KeyListener1 implements KeyListener {
+
+ @Override
+ public int getInputType() {
+ return InputType.TYPE_CLASS_TEXT;
+ }
+
+ @Override
+ public boolean onKeyDown(View view, Editable text, int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean onKeyUp(View view, Editable text, int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean onKeyOther(View view, Editable text, KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public void clearMetaKeyState(View view, Editable content, int states) {
+ }
+ }
+
+ public static class KeyListener2 extends KeyListener1 {
+
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/User.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/User.java
new file mode 100644
index 0000000..1107265
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/User.java
@@ -0,0 +1,36 @@
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+
+public class User {
+ @Bindable
+ private User friend;
+ @Bindable
+ private String name;
+ @Bindable
+ private String fullName;
+
+ public User getFriend() {
+ return friend;
+ }
+
+ public void setFriend(User friend) {
+ this.friend = friend;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getFullName() {
+ return fullName;
+ }
+
+ public void setFullName(String fullName) {
+ this.fullName = fullName;
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ViewBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ViewBindingObject.java
new file mode 100644
index 0000000..a955e81
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ViewBindingObject.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+import android.databinding.adapters.ViewBindingAdapter;
+import android.databinding.testapp.R;
+import android.view.View;
+
+public class ViewBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private int mBackgroundTint = 0xFF00FF00;
+ @Bindable
+ private boolean mFadeScrollbars = false;
+ @Bindable
+ private int mNextFocusForward = R.id.padding;
+ @Bindable
+ private int mNextFocusLeft = R.id.paddingStartEnd;
+ @Bindable
+ private int mNextFocusRight = R.id.paddingTopBottom;
+ @Bindable
+ private int mNextFocusUp = R.id.backgroundTint;
+ @Bindable
+ private int mNextFocusDown = R.id.fadeScrollbars;
+ @Bindable
+ private int mRequiresFadingEdge = ViewBindingAdapter.FADING_EDGE_VERTICAL;
+ @Bindable
+ private int mScrollbarDefaultDelayBeforeFade = 300;
+ @Bindable
+ private int mScrollbarFadeDuration = 400;
+ @Bindable
+ private int mScrollbarSize = 10;
+ @Bindable
+ private int mScrollbarStyle = View.SCROLLBARS_INSIDE_OVERLAY;
+ @Bindable
+ private float mTransformPivotX = 9;
+ @Bindable
+ private float mTransformPivotY = 8;
+ @Bindable
+ private int mPadding = 11;
+ @Bindable
+ private int mPaddingBottom = 12;
+ @Bindable
+ private int mPaddingTop = 13;
+ @Bindable
+ private int mPaddingLeft = 14;
+ @Bindable
+ private int mPaddingRight = 15;
+ @Bindable
+ private int mPaddingStart = 16;
+ @Bindable
+ private int mPaddingEnd = 17;
+
+ public int getBackgroundTint() {
+ return mBackgroundTint;
+ }
+
+ public int getScrollbarFadeDuration() {
+ return mScrollbarFadeDuration;
+ }
+
+ public boolean getFadeScrollbars() {
+ return mFadeScrollbars;
+ }
+
+ public int getNextFocusDown() {
+ return mNextFocusDown;
+ }
+
+ public int getNextFocusForward() {
+ return mNextFocusForward;
+ }
+
+ public int getNextFocusLeft() {
+ return mNextFocusLeft;
+ }
+
+ public int getNextFocusRight() {
+ return mNextFocusRight;
+ }
+
+ public int getNextFocusUp() {
+ return mNextFocusUp;
+ }
+
+ public int getRequiresFadingEdge() {
+ return mRequiresFadingEdge;
+ }
+
+ public int getScrollbarDefaultDelayBeforeFade() {
+ return mScrollbarDefaultDelayBeforeFade;
+ }
+
+ public int getScrollbarSize() {
+ return mScrollbarSize;
+ }
+
+ public int getScrollbarStyle() {
+ return mScrollbarStyle;
+ }
+
+ public float getTransformPivotX() {
+ return mTransformPivotX;
+ }
+
+ public float getTransformPivotY() {
+ return mTransformPivotY;
+ }
+
+ public int getPadding() {
+ return mPadding;
+ }
+
+ public int getPaddingBottom() {
+ return mPaddingBottom;
+ }
+
+ public int getPaddingEnd() {
+ return mPaddingEnd;
+ }
+
+ public int getPaddingLeft() {
+ return mPaddingLeft;
+ }
+
+ public int getPaddingRight() {
+ return mPaddingRight;
+ }
+
+ public int getPaddingStart() {
+ return mPaddingStart;
+ }
+
+ public int getPaddingTop() {
+ return mPaddingTop;
+ }
+
+ public void changeValues() {
+ mBackgroundTint = 0xFFFF0000;
+ mFadeScrollbars = true;
+ mNextFocusForward = R.id.paddingStartEnd;
+ mNextFocusLeft = R.id.paddingTopBottom;
+ mNextFocusRight = R.id.backgroundTint;
+ mNextFocusUp = R.id.fadeScrollbars;
+ mNextFocusDown = R.id.padding;
+ mRequiresFadingEdge = ViewBindingAdapter.FADING_EDGE_HORIZONTAL;
+ mScrollbarDefaultDelayBeforeFade = 400;
+ mScrollbarFadeDuration = 500;
+ mScrollbarSize = 11;
+ mScrollbarStyle = View.SCROLLBARS_INSIDE_INSET;
+ mTransformPivotX = 7;
+ mTransformPivotY = 6;
+ mPadding = 110;
+ mPaddingBottom = 120;
+ mPaddingTop = 130;
+ mPaddingLeft = 140;
+ mPaddingRight = 150;
+ mPaddingStart = 160;
+ mPaddingEnd = 170;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ViewGroupBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ViewGroupBindingObject.java
new file mode 100644
index 0000000..107fec6
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ViewGroupBindingObject.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+
+public class ViewGroupBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private boolean mAlwaysDrawnWithCache;
+ @Bindable
+ private boolean mAnimationCache;
+ @Bindable
+ private boolean mSplitMotionEvents;
+ @Bindable
+ private boolean mAnimateLayoutChanges;
+
+ public boolean isAlwaysDrawnWithCache() {
+ return mAlwaysDrawnWithCache;
+ }
+
+ public boolean isAnimationCache() {
+ return mAnimationCache;
+ }
+
+ public boolean isSplitMotionEvents() {
+ return mSplitMotionEvents;
+ }
+
+ public boolean isAnimateLayoutChanges() {
+ return mAnimateLayoutChanges;
+ }
+
+ public void changeValues() {
+ mAlwaysDrawnWithCache = true;
+ mAnimationCache = true;
+ mAnimateLayoutChanges = true;
+ mSplitMotionEvents = true;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ViewStubBindingObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ViewStubBindingObject.java
new file mode 100644
index 0000000..3e88e91
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/ViewStubBindingObject.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp.vo;
+
+import android.databinding.Bindable;
+import android.databinding.testapp.R;
+
+public class ViewStubBindingObject extends BindingAdapterBindingObject {
+ @Bindable
+ private int mLayout = R.layout.table_layout_adapter_test;
+
+ public int getLayout() {
+ return mLayout;
+ }
+
+ public void changeValues() {
+ mLayout = R.layout.auto_complete_text_view_adapter_test;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/drawable-hdpi/ic_launcher.png b/tools/data-binding/integration-tests/TestApp/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/drawable-mdpi/ic_launcher.png b/tools/data-binding/integration-tests/TestApp/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/drawable-xhdpi/ic_launcher.png b/tools/data-binding/integration-tests/TestApp/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/tools/data-binding/integration-tests/TestApp/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4df1894
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout-land/multi_res_layout.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout-land/multi_res_layout.xml
new file mode 100644
index 0000000..594c3fe
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout-land/multi_res_layout.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bind="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="objectInLand" type="android.databinding.testapp.vo.NotBindableVo"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:id="@+id/objectInLandTextView"
+ android:text="@{objectInLand.stringValue}"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:id="@+id/objectInDefaultTextView"
+ android:text="@{objectInDefault.stringValue}"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:id="@+id/objectInDefaultTextView2"
+ android:text="@{objectInDefault.stringValue}"/>
+
+ <include layout="@layout/included_layout" android:id="@+id/includedLayoutConflict"
+ bind:innerObject="@{objectInLand}"
+ bind:innerValue='@{"modified " + objectInLand.intValue}' />
+ <include layout="@layout/basic_binding" android:id="@+id/includedLayoutShared"
+ bind:a="@{objectInDefault.stringValue}"
+ />
+ <include layout="@layout/conditional_binding" android:id="@+id/includedLayoutLand"
+ bind:obj2="@{objectInDefault}"
+ />
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/abs_list_view_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/abs_list_view_adapter_test.xml
new file mode 100644
index 0000000..4ff0ba8
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/abs_list_view_adapter_test.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:listSelector="@{obj.listSelector}"
+ android:scrollingCache="@{obj.scrollingCache}"
+ android:smoothScrollbar="@{obj.smoothScrollbar}"
+ >
+ <variable name="obj" type="android.databinding.testapp.vo.AbsListViewBindingObject"/>
+</ListView>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/abs_seek_bar_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/abs_seek_bar_adapter_test.xml
new file mode 100644
index 0000000..0fafb2d
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/abs_seek_bar_adapter_test.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.AbsSeekBarBindingObject"/>
+ <SeekBar android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:thumbTint="@{obj.thumbTint}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/abs_spinner_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/abs_spinner_adapter_test.xml
new file mode 100644
index 0000000..047b107
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/abs_spinner_adapter_test.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.AbsSpinnerBindingObject"/>
+ <Spinner android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:entries="@{obj.entries}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/auto_complete_text_view_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/auto_complete_text_view_adapter_test.xml
new file mode 100644
index 0000000..b999841
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/auto_complete_text_view_adapter_test.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.AutoCompleteTextViewBindingObject"/>
+ <AutoCompleteTextView android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:completionThreshold="@{obj.completionThreshold}"
+ android:popupBackground="@{obj.popupBackground}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/basic_binding.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/basic_binding.xml
new file mode 100644
index 0000000..6e75fb2
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/basic_binding.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bind="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="a" type="String"/>
+ <variable name="b" type="String"/>
+ <TextView
+ android:id="@+id/textView"
+ android:text="@{a + b}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/basic_dependant_binding.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/basic_dependant_binding.xml
new file mode 100644
index 0000000..453348a
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/basic_dependant_binding.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bind="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj1" type="android.databinding.testapp.vo.NotBindableVo"/>
+ <variable name="obj2" type="android.databinding.testapp.vo.NotBindableVo"/>
+ <TextView
+ android:id="@+id/textView1"
+ android:text="@{obj1.stringValue}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/textView2"
+ android:text="@{obj2.stringValue}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/mergedTextView1"
+ android:text="@{obj1.mergeStringFields(obj2)}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/mergedTextView2"
+ android:text="@{obj2.mergeStringFields(obj1)}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <TextView
+ android:id="@+id/rawStringMerge"
+ android:text="@{obj1.stringValue + obj2.stringValue}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/bind_to_final.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/bind_to_final.xml
new file mode 100644
index 0000000..f578585
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/bind_to_final.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.PublicFinalTestVo"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:id="@+id/text_view"
+ android:text="@{obj.myField}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/bind_to_final_observable.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/bind_to_final_observable.xml
new file mode 100644
index 0000000..15d62fe
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/bind_to_final_observable.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.PublicFinalWithObservableTestVo"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:id="@+id/text_view"
+ android:text="@{obj.myFinalVo.val}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/bracket_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/bracket_test.xml
new file mode 100644
index 0000000..49c2872
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/bracket_test.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ >
+ <variable name="array" type="String[]"/>
+ <variable name="sparseArray" type="android.util.SparseArray<String>"/>
+ <variable name="sparseBooleanArray" type="android.util.SparseBooleanArray"/>
+ <variable name="sparseIntArray" type="android.util.SparseIntArray"/>
+ <variable name="sparseLongArray" type="android.util.SparseLongArray"/>
+ <variable name="longSparseArray" type="android.util.LongSparseArray<String>"/>
+
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/arrayText"
+ android:text="@{array[0]}"/>
+
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/sparseArrayText"
+ android:text='@{sparseArray[0]}'/>
+
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/sparseBooleanArrayText"
+ android:text='@{"" + sparseBooleanArray[0]}'/>
+
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/sparseIntArrayText"
+ android:text='@{"" + sparseIntArray[0]}'/>
+
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/sparseLongArrayText"
+ android:text='@{"" + sparseLongArray[0]}'/>
+
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/longSparseArrayText"
+ android:text='@{longSparseArray[0]}'/>
+
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/cast_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/cast_test.xml
new file mode 100644
index 0000000..803b5ee
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/cast_test.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <import type="java.util.Collection"/>
+ <import type="java.util.ArrayList"/>
+ <import type="java.util.Map"/>
+ <variable name="list" type="Collection<String>"/>
+ <variable name="map" type="Object"/>
+
+ <TextView
+ android:id="@+id/textView0"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{((ArrayList<String>)list)[0]}"/>
+ <TextView
+ android:id="@+id/textView1"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{((Map<String, String>)map)[`hello`]}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/checked_text_view_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/checked_text_view_adapter_test.xml
new file mode 100644
index 0000000..02c50c8
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/checked_text_view_adapter_test.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.CheckedTextViewBindingObject"/>
+ <CheckedTextView
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:checkMark="@{obj.checkMark}"
+ android:checkMarkTint="@{obj.checkMarkTint}"/>
+
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/compound_button_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/compound_button_adapter_test.xml
new file mode 100644
index 0000000..6f61825
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/compound_button_adapter_test.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.CompoundButtonBindingObject"/>
+ <CheckBox
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:buttonTint="@{obj.buttonTint}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/conditional_binding.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/conditional_binding.xml
new file mode 100644
index 0000000..70376a0
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/conditional_binding.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bind="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj1" type="android.databinding.testapp.vo.NotBindableVo"/>
+ <variable name="obj2" type="android.databinding.testapp.vo.NotBindableVo"/>
+ <variable name="obj3" type="android.databinding.testapp.vo.NotBindableVo"/>
+ <variable name="cond1" type="boolean"/>
+ <variable name="cond2" type="boolean"/>
+ <TextView
+ android:id="@+id/textView"
+ android:text="@{cond1 ? cond2 ? obj1.stringValue : obj2.stringValue : obj3.stringValue}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/find_method_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/find_method_test.xml
new file mode 100644
index 0000000..33a3746
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/find_method_test.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.FindMethodBindingObject"/>
+ <import type="android.databinding.testapp.vo.FindMethodBindingObject.Bar"/>
+ <variable name="obj2" type="Bar<String>"/>
+ <import type="android.databinding.testapp.vo.FindMethodBindingObject"/>
+ <import type="android.databinding.testapp.vo.FindMethodBindingObject" alias="FMBO"/>
+ <TextView
+ android:id="@+id/textView0"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{obj.method(1)}"/>
+ <TextView
+ android:id="@+id/textView1"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{obj.method(1.25f)}"/>
+ <TextView
+ android:id="@+id/textView2"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{obj.method(`hello`)}"/>
+ <TextView
+ android:id="@+id/textView3"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{obj.method((java.lang.Integer) 1)}"/>
+ <TextView
+ android:id="@+id/textView4"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{obj.inheritedMethod()}"/>
+ <TextView
+ android:id="@+id/textView5"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{obj.inheritedMethod(2)}"/>
+ <TextView
+ android:id="@+id/textView6"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{obj.method()}"/>
+ <TextView
+ android:id="@+id/textView7"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{android.databinding.testapp.vo.FindMethodBindingObject.staticMethod()}"/>
+ <TextView
+ android:id="@+id/textView8"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{android.databinding.testapp.vo.FindMethodBindingObject.foo.bar}"/>
+ <TextView
+ android:id="@+id/textView9"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{FindMethodBindingObject.staticMethod()}"/>
+ <TextView
+ android:id="@+id/textView10"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{FindMethodBindingObject.foo.bar}"/>
+ <TextView
+ android:id="@+id/textView11"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{FMBO.staticMethod()}"/>
+ <TextView
+ android:id="@+id/textView12"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{FMBO.foo.bar}"/>
+ <!-- The following are just to test duplicate expressions -->
+ <TextView
+ android:id="@+id/textView13"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{FMBO.staticMethod()}"/>
+ <TextView
+ android:id="@+id/textView14"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{FMBO.foo.bar}"/>
+ <!-- Imported classes -->
+ <TextView
+ android:id="@+id/textView15"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{obj2.method(`hello`)}"/>
+ <!-- confusing parameters may interfere with compile step -->
+ <TextView
+ android:id="@+id/textView16"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{obj.confusingPrimitive((Integer)1)}"/>
+ <TextView
+ android:id="@+id/textView17"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{`` + (obj.confusingPrimitive(2) / 2)}"/>
+ <TextView
+ android:id="@+id/textView18"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{obj.confusingInheritance(`hello`)}"/>
+ <TextView
+ android:id="@+id/textView19"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{obj.confusingTypeArgs(obj.getMap())}"/>
+ <TextView
+ android:id="@+id/textView20"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{obj.confusingParam(`hello`)}"/>
+ <TextView
+ android:id="@+id/textView21"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{obj.getList()[0]}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/frame_layout_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/frame_layout_adapter_test.xml
new file mode 100644
index 0000000..7c9b860
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/frame_layout_adapter_test.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.FrameLayoutBindingObject"/>
+ <FrameLayout
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:foregroundTint="@{obj.foregroundTint}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/image_view_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/image_view_adapter_test.xml
new file mode 100644
index 0000000..a3ce7f4
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/image_view_adapter_test.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.ImageViewBindingObject"/>
+ <ImageView
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:src="@{obj.src}"
+ android:tint="@{obj.tint}"
+ android:tintMode="@{obj.tintMode}"
+ />
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/included_layout.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/included_layout.xml
new file mode 100644
index 0000000..5b0bf4c
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/included_layout.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bind="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="innerObject" type="android.databinding.testapp.vo.NotBindableVo"/>
+ <variable name="innerValue" type="java.lang.String"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:id="@+id/innerTextView"
+ android:text="@{innerValue + innerObject.stringValue}"
+ />
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/inner_cannot_read_dependency.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/inner_cannot_read_dependency.xml
new file mode 100644
index 0000000..2ad1980
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/inner_cannot_read_dependency.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.BasicObject"/>
+ <TextView
+ android:id="@+id/text_view"
+ android:text='@{obj.field1 + " " + (obj.field2 ?? "")}'
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/layout_with_include.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/layout_with_include.xml
new file mode 100644
index 0000000..6504e4c
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/layout_with_include.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bind="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="outerObject" type="android.databinding.testapp.vo.NotBindableVo"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:id="@+id/outerTextView"
+ android:text="@{outerObject.stringValue}"/>
+ <!-- TODO test id collision-->
+ <include layout="@layout/included_layout" android:id="@+id/includedLayout"
+ bind:innerObject="@{outerObject}"
+ bind:innerValue="@{`modified ` + outerObject.intValue}"
+ />
+ <include layout="@layout/plain_layout" android:id="@+id/plainLayout"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/leak_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/leak_test.xml
new file mode 100644
index 0000000..3dbf2f5
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/leak_test.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="name" type="String"/>
+ <TextView
+ android:id="@+id/textView"
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{name}"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/linear_layout_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/linear_layout_adapter_test.xml
new file mode 100644
index 0000000..0b08f17
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/linear_layout_adapter_test.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.LinearLayoutBindingObject"/>
+ <LinearLayout
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:orientation="horizontal"
+ android:divider="@{obj.divider}"
+ android:measureWithLargestChild="@{obj.measureWithLargestChild}"
+ />
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/multi_res_layout.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/multi_res_layout.xml
new file mode 100644
index 0000000..99c7d817
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/multi_res_layout.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bind="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="objectInDefault" type="android.databinding.testapp.vo.NotBindableVo"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:id="@+id/objectInDefaultTextView"
+ android:text="@{objectInDefault.stringValue}"/>
+ <EditText android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:id="@+id/objectInDefaultTextView2"
+ android:text="@{objectInDefault.stringValue}"/>
+
+ <include layout="@layout/basic_binding" android:id="@+id/includedLayoutConflict"
+ bind:a="@{objectInDefault.stringValue}"
+ />
+ <include layout="@layout/basic_binding" android:id="@+id/includedLayoutShared"
+ bind:a="@{objectInDefault.stringValue}"
+ />
+ <include layout="@layout/conditional_binding" android:id="@+id/includedLayoutPort"
+ bind:cond1="@{objectInDefault == null}"
+ />
+
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/new_api_layout.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/new_api_layout.xml
new file mode 100644
index 0000000..26c4e95
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/new_api_layout.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/myContainer"
+ android:addChildrenForAccessibility="@{children}"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="elevation" type="float"/>
+ <variable name="name" type="java.lang.String"/>
+ <variable name="children" type="java.util.ArrayList<android.view.View>"/>
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/textView"
+ android:text="@{name}" android:elevation="@{elevation}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/no_id_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/no_id_test.xml
new file mode 100644
index 0000000..32f06f4
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/no_id_test.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="@{orientation}"
+ >
+ <variable name="name" type="String"/>
+ <variable name="orientation" type="int"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{name}" android:tag="hello world"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{name}"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{name}" android:tag="@string/app_name"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{name}" android:tag="@android:string/ok"/>
+ <TextView android:id="@+id/textView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="hello"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/observable_field_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/observable_field_test.xml
new file mode 100644
index 0000000..1db044f
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/observable_field_test.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.ObservableFieldBindingObject"/>
+ <TextView
+ android:id="@+id/bField"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{`` + obj.bField}"/>
+ <TextView
+ android:id="@+id/tField"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{`` + obj.tField}"/>
+ <TextView
+ android:id="@+id/sField"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{`` + obj.sField}"/>
+ <TextView
+ android:id="@+id/cField"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{`` + obj.cField}"/>
+ <TextView
+ android:id="@+id/iField"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{`` + obj.iField}"/>
+ <TextView
+ android:id="@+id/lField"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{`` + obj.lField}"/>
+ <TextView
+ android:id="@+id/fField"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{`` + obj.fField}"/>
+ <TextView
+ android:id="@+id/dField"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{`` + obj.dField}"/>
+ <TextView
+ android:id="@+id/oField"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{obj.oField}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/observable_with_not_bindable_field.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/observable_with_not_bindable_field.xml
new file mode 100644
index 0000000..bde2dd4
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/observable_with_not_bindable_field.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.ObservableWithNotBindableFieldObject"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:id="@+id/text_view"
+ android:text="@{obj.data}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/plain_layout.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/plain_layout.xml
new file mode 100644
index 0000000..2132370
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/plain_layout.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/progress_bar_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/progress_bar_adapter_test.xml
new file mode 100644
index 0000000..4fb8235
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/progress_bar_adapter_test.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.ProgressBarBindingObject"/>
+ <ProgressBar
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:indeterminateTint="@{obj.indeterminateTint}"
+ android:progressTint="@{obj.progressTint}"
+ android:secondaryProgressTint="@{obj.secondaryProgressTint}"
+ />
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/radio_group_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/radio_group_adapter_test.xml
new file mode 100644
index 0000000..efb6038
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/radio_group_adapter_test.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.RadioGroupBindingObject"/>
+ <RadioGroup
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:checkedButton="@{obj.checkedButton}"
+ >
+ <RadioButton android:layout_width="match_parent" android:layout_height="wrap_content"
+ android:text="One" android:id="@+id/choiceOne"/>
+ <RadioButton android:layout_width="match_parent" android:layout_height="wrap_content"
+ android:text="Two" android:id="@+id/choiceTwo"/>
+ </RadioGroup>
+
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/read_complex_ternary.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/read_complex_ternary.xml
new file mode 100644
index 0000000..a50cda2
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/read_complex_ternary.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="user" type="android.databinding.testapp.vo.User"/>
+ <TextView
+ android:id="@+id/text_view"
+ android:text='@{user.friend == null ? "?" : (user.friend.name + "is friend of " + user.name)}'
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/resource_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/resource_test.xml
new file mode 100644
index 0000000..59ecc35
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/resource_test.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ >
+ <variable name="count" type="int"/>
+ <variable name="title" type="String"/>
+ <variable name="lastName" type="String"/>
+ <variable name="base" type="int"/>
+ <variable name="pbase" type="int"/>
+
+ <TextView
+ android:id="@+id/textView0"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{@string/nameWithTitle(title, lastName)}"/>
+
+ <TextView
+ android:id="@+id/textView1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{@plurals/orange(count)}"/>
+ <TextView
+ android:id="@+id/fractionNoParameters"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{`` + @fraction/myFraction}"/>
+ <TextView
+ android:id="@+id/fractionOneParameter"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{`` + @fraction/myFraction(base)}"/>
+ <TextView
+ android:id="@+id/fractionTwoParameters"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{`` + @fraction/myParentFraction(base, pbase)}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/spinner_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/spinner_adapter_test.xml
new file mode 100644
index 0000000..a82e7cea
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/spinner_adapter_test.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.SpinnerBindingObject"/>
+ <Spinner
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:popupBackground="@{obj.popupBackground}"
+ />
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/switch_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/switch_adapter_test.xml
new file mode 100644
index 0000000..4bb1b84
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/switch_adapter_test.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.SwitchBindingObject"/>
+ <Switch
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:thumb="@{obj.thumb}"
+ android:track="@{obj.track}"
+ />
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/tab_widget_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/tab_widget_adapter_test.xml
new file mode 100644
index 0000000..4167ae4
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/tab_widget_adapter_test.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.TabWidgetBindingObject"/>
+ <TabWidget android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:divider="@{obj.divider}"
+ android:tabStripEnabled="@{obj.tabStripEnabled}"
+ android:tabStripLeft="@{obj.tabStripLeft}"
+ android:tabStripRight="@{obj.tabStripRight}"
+ />
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/table_layout_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/table_layout_adapter_test.xml
new file mode 100644
index 0000000..8d53bce
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/table_layout_adapter_test.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.TableLayoutBindingObject"/>
+ <TableLayout
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:divider="@{obj.divider}"
+ android:collapseColumns="@{obj.collapseColumns}"
+ android:shrinkColumns="@{obj.shrinkColumns}"
+ android:stretchColumns="@{obj.stretchColumns}"
+ >
+ <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content">
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="Hello"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="Happy"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="World"/>
+ </TableRow>
+ <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content">
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="Goodbye"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="Cruel"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="World"/>
+ </TableRow>
+ </TableLayout>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/text_view_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/text_view_adapter_test.xml
new file mode 100644
index 0000000..dc6fd29
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/text_view_adapter_test.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.TextViewBindingObject"/>
+ <TextView android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/numericText"
+ android:numeric="@{obj.numeric}"
+ />
+ <TextView android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/textDrawableNormal"
+ android:drawableBottom="@{obj.drawableBottom}"
+ android:drawableLeft="@{obj.drawableLeft}"
+ android:drawableRight="@{obj.drawableRight}"
+ android:drawableTop="@{obj.drawableTop}"
+ />
+ <TextView android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/textDrawableStartEnd"
+ android:drawableStart="@{obj.drawableStart}"
+ android:drawableEnd="@{obj.drawableEnd}"
+ />
+ <TextView android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/textView"
+ android:autoLink="@{obj.autoLink}"
+ android:drawablePadding="@{obj.drawablePadding}"
+ android:scrollHorizontally="@{obj.scrollHorizontally}"
+ android:textColorHighlight="@{obj.textColorHighlight}"
+ android:textColorHint="@{obj.textColorHint}"
+ android:textColorLink="@{obj.textColorLink}"
+ android:autoText="@{obj.autoText}"
+ android:capitalize="@{obj.capitalize}"
+ android:imeActionLabel="@{obj.imeActionLabel}"
+ android:imeActionId="@{obj.imeActionId}"
+ android:lineSpacingExtra="@{obj.lineSpacingExtra}"
+ android:lineSpacingMultiplier="@{obj.lineSpacingMultiplier}"
+ android:maxLength="@{obj.maxLength}"
+ android:shadowColor="@{obj.shadowColor}"
+ android:shadowDx="@{obj.shadowDx}"
+ android:shadowDy="@{obj.shadowDy}"
+ android:shadowRadius="@{obj.shadowRadius}"
+ android:textSize="@{obj.textSize}"
+ />
+ <TextView android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/textAllCaps"
+ android:textAllCaps="@{obj.textAllCaps}"
+ />
+ <TextView android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/textBufferType"
+ android:bufferType="@{obj.bufferType}"
+ />
+ <TextView android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/textInputType"
+ android:inputType="@{obj.inputType}"
+ />
+ <TextView android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/textDigits"
+ android:digits="@{obj.digits}"
+ />
+ <TextView android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/textPhoneNumber"
+ android:phoneNumber="@{obj.phoneNumber}"
+ />
+ <TextView android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/textInputMethod"
+ android:inputMethod="@{obj.inputMethod}"
+ />
+
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_adapter_test.xml
new file mode 100644
index 0000000..73660ce
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_adapter_test.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+ <variable name="obj" type="android.databinding.testapp.vo.ViewBindingObject"/>
+ <View
+ android:id="@+id/padding"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:padding="@{obj.padding}"
+ />
+ <View
+ android:id="@+id/paddingStartEnd"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:paddingEnd="@{obj.paddingEnd}"
+ android:paddingStart="@{obj.paddingStart}"
+ />
+ <View
+ android:id="@+id/paddingTopBottom"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:paddingBottom="@{obj.paddingBottom}"
+ android:paddingTop="@{obj.paddingTop}"
+ />
+ <View
+ android:id="@+id/paddingLeftRight"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:paddingLeft="@{obj.paddingLeft}"
+ android:paddingRight="@{obj.paddingRight}"
+ />
+ <View
+ android:id="@+id/backgroundTint"
+ android:backgroundTint="@{obj.backgroundTint}"
+ android:layout_width="10dp"
+ android:layout_height="10dp"/>
+ <View
+ android:id="@+id/fadeScrollbars"
+ android:fadeScrollbars="@{obj.fadeScrollbars}"
+ android:layout_width="10dp"
+ android:layout_height="10dp"/>
+ <View
+ android:id="@+id/nextFocus"
+ android:nextFocusForward="@{obj.nextFocusForward}"
+ android:nextFocusLeft="@{obj.nextFocusLeft}"
+ android:nextFocusRight="@{obj.nextFocusRight}"
+ android:nextFocusUp="@{obj.nextFocusUp}"
+ android:nextFocusDown="@{obj.nextFocusDown}"
+ android:layout_width="10dp"
+ android:layout_height="10dp"/>
+ <View
+ android:id="@+id/requiresFadingEdge"
+ android:requiresFadingEdge="@{obj.requiresFadingEdge}"
+ android:layout_width="10dp"
+ android:layout_height="10dp"/>
+ <View
+ android:id="@+id/scrollbar"
+ android:scrollbarDefaultDelayBeforeFade="@{obj.scrollbarDefaultDelayBeforeFade}"
+ android:scrollbarFadeDuration="@{obj.scrollbarFadeDuration}"
+ android:scrollbarSize="@{obj.scrollbarSize}"
+ android:scrollbarStyle="@{obj.scrollbarStyle}"
+ android:layout_width="10dp"
+ android:layout_height="10dp"/>
+ <View
+ android:id="@+id/transformPivot"
+ android:transformPivotX="@{obj.transformPivotX}"
+ android:transformPivotY="@{obj.transformPivotY}"
+ android:layout_width="10dp"
+ android:layout_height="10dp"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_group_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_group_adapter_test.xml
new file mode 100644
index 0000000..af40a27
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_group_adapter_test.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.ViewGroupBindingObject"/>
+ <FrameLayout android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:alwaysDrawnWithCache="@{obj.alwaysDrawnWithCache}"
+ android:animationCache="@{obj.animationCache}"
+ android:splitMotionEvents="@{obj.splitMotionEvents}"
+ android:animateLayoutChanges="@{obj.animateLayoutChanges}"
+ />
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub.xml
new file mode 100644
index 0000000..af0796e
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ xmlns:bind="http://schemas.android.com/apk/res-auto">
+ <variable name="viewStubVisibility" type="int"/>
+ <variable name="firstName" type="String"/>
+ <variable name="lastName" type="String"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{firstName}"
+ android:id="@+id/firstName"
+ />
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{lastName}"
+ android:id="@+id/lastName"
+ />
+
+ <ViewStub android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/viewStub"
+ android:visibility="@{viewStubVisibility}"
+ android:layout="@layout/view_stub_contents"
+ bind:firstName="@{firstName}"
+ bind:lastName="@{lastName}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub_adapter_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub_adapter_test.xml
new file mode 100644
index 0000000..e0b922b
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub_adapter_test.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="obj" type="android.databinding.testapp.vo.ViewStubBindingObject"/>
+ <ViewStub android:layout_width="match_parent" android:layout_height="match_parent"
+ android:id="@+id/view"
+ android:layout="@{obj.layout}"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub_contents.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub_contents.xml
new file mode 100644
index 0000000..9305189
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/view_stub_contents.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <variable name="firstName" type="String"/>
+ <variable name="lastName" type="String"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{firstName}"
+ android:id="@+id/firstNameContents"/>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="@{lastName}"
+ android:id="@+id/lastNameContents"/>
+</LinearLayout>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/values-v21/styles.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..0a2c6bec
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <style name="AppTheme" parent="android:Theme.Material.Light">
+ </style>
+</resources>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/values/fractions.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/values/fractions.xml
new file mode 100644
index 0000000..8817316
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/values/fractions.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+
+ <!-- Base application theme. -->
+ <fraction name="myFraction">150%</fraction>
+ <fraction name="myParentFraction">300%p</fraction>
+
+</resources>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/values/strings.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e53e327
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/values/strings.xml
@@ -0,0 +1,22 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <string name="app_name">TestApp</string>
+ <string name="rain">Rain</string>
+ <string name="nameWithTitle">%1$s %2$s</string>
+ <plurals name="orange">
+ <item quantity="one">orange</item>
+ <item quantity="other">oranges</item>
+ </plurals>
+</resources>
diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/values/styles.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..c0d5471
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/values/styles.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="android:Theme.Holo">
+ <!-- Customize your theme here. -->
+ </style>
+
+</resources>
diff --git a/tools/data-binding/integration-tests/TestApp/build.gradle b/tools/data-binding/integration-tests/TestApp/build.gradle
new file mode 100644
index 0000000..85d7f88
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/build.gradle
@@ -0,0 +1,29 @@
+buildscript {
+ def Properties dataBindingProperties = new Properties()
+ dataBindingProperties.load(new FileInputStream("${projectDir}/../../databinding.properties"))
+ dataBindingProperties.mavenRepoDir = "${projectDir}/../../${dataBindingProperties.mavenRepoName}"
+ ext.config = dataBindingProperties
+ println "loaded config"
+
+ repositories {
+ jcenter()
+ maven {
+ url config.mavenRepoDir
+ }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.1.3'
+ classpath "com.android.databinding:dataBinder:${config.snapshotVersion}"
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ maven {
+ url config.mavenRepoDir
+ }
+ }
+}
diff --git a/tools/data-binding/integration-tests/TestApp/gradle.properties b/tools/data-binding/integration-tests/TestApp/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/tools/data-binding/integration-tests/TestApp/gradle/wrapper/gradle-wrapper.jar b/tools/data-binding/integration-tests/TestApp/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/tools/data-binding/integration-tests/TestApp/gradle/wrapper/gradle-wrapper.properties b/tools/data-binding/integration-tests/TestApp/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0c71e76
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/tools/data-binding/integration-tests/TestApp/gradlew b/tools/data-binding/integration-tests/TestApp/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/tools/data-binding/integration-tests/TestApp/gradlew.bat b/tools/data-binding/integration-tests/TestApp/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/tools/data-binding/integration-tests/TestApp/settings.gradle b/tools/data-binding/integration-tests/TestApp/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/tools/data-binding/integration-tests/TestApp/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/tools/data-binding/library/build.gradle b/tools/data-binding/library/build.gradle
new file mode 100644
index 0000000..4679d3f
--- /dev/null
+++ b/tools/data-binding/library/build.gradle
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:${config.androidPluginVersion}"
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1"
+
+ defaultConfig {
+ minSdkVersion 7
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+ packagingOptions {
+ exclude 'META-INF/services/javax.annotation.processing.Processor'
+ exclude 'META-INF/LICENSE.txt'
+ exclude 'META-INF/NOTICE.txt'
+ exclude 'android/databinding/DataBinderMapper.class'
+ }
+}
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile project(":baseLibrary")
+ compile 'com.android.support:support-v4:+'
+}
+
+configurations {
+ jarArchives
+}
+
+
+//create jar tasks
+android.libraryVariants.all { variant ->
+ def name = variant.buildType.name
+
+ if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
+ return; // Skip debug builds.
+ }
+ // @Jar version is needed to run compiler tests
+ def task = project.tasks.create "jar${name.capitalize()}", Jar
+ task.dependsOn variant.javaCompile
+ task.from variant.javaCompile.destinationDir
+ artifacts.add('jarArchives', task);
+}
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ pom.artifactId = 'library'
+ }
+ }
+}
+
+afterEvaluate {
+ tasks['packageReleaseJar'].exclude('android/databinding/DataBinderMapper.*')
+ tasks['packageDebugJar'].exclude('android/databinding/DataBinderMapper.*')
+}
+
+uploadJarArchives {
+ repositories {
+ mavenDeployer {
+ repository(url: "file://${config.mavenRepoDir}")
+ pom.artifactId = "libraryJar"
+ }
+ }
+}
+
+uploadArchives.dependsOn uploadJarArchives
\ No newline at end of file
diff --git a/tools/data-binding/library/src/main/AndroidManifest.xml b/tools/data-binding/library/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..8596caf
--- /dev/null
+++ b/tools/data-binding/library/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<!--
+ Copyright (C) 2014 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.databinding.library">
+
+</manifest>
diff --git a/tools/data-binding/library/src/main/java/android/databinding/BaseObservable.java b/tools/data-binding/library/src/main/java/android/databinding/BaseObservable.java
new file mode 100644
index 0000000..3aa9d35
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/BaseObservable.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding;
+
+public class BaseObservable implements Observable {
+ private PropertyChangeRegistry mCallbacks;
+
+ public BaseObservable() {
+ }
+
+ @Override
+ public synchronized void addOnPropertyChangedListener(OnPropertyChangedListener listener) {
+ if (mCallbacks == null) {
+ mCallbacks = new PropertyChangeRegistry();
+ }
+ mCallbacks.add(listener);
+ }
+
+ @Override
+ public synchronized void removeOnPropertyChangedListener(OnPropertyChangedListener listener) {
+ if (mCallbacks != null) {
+ mCallbacks.remove(listener);
+ }
+ }
+
+ public synchronized void notifyChange() {
+ if (mCallbacks != null) {
+ mCallbacks.notifyCallbacks(this, 0, null);
+ }
+ }
+
+ public void notifyPropertyChanged(int fieldId) {
+ if (mCallbacks != null) {
+ mCallbacks.notifyCallbacks(this, fieldId, null);
+ }
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/DataBinderMapper.java b/tools/data-binding/library/src/main/java/android/databinding/DataBinderMapper.java
new file mode 100644
index 0000000..9fc7f4b
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/DataBinderMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding;
+
+import android.view.View;
+
+/**
+ * This class will be stripped from the jar and then replaced by the annotation processor
+ * as part of the code generation step. This class's existence is just to ensure that
+ * compile works and no reflection is needed to access the generated class.
+ */
+class DataBinderMapper {
+ public ViewDataBinding getDataBinder(View view, int layoutId) {
+ return null;
+ }
+ public int getId(String key) {
+ return 0;
+ }
+ public static int TARGET_MIN_SDK = 0;
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/DataBindingUtil.java b/tools/data-binding/library/src/main/java/android/databinding/DataBindingUtil.java
new file mode 100644
index 0000000..cb5d209
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/DataBindingUtil.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Utility class to create {@link ViewDataBinding} from layouts.
+ */
+public class DataBindingUtil {
+ private static DataBinderMapper sMapper = new DataBinderMapper();
+
+ public static <T extends ViewDataBinding> T inflate(Context context, int layoutId,
+ ViewGroup parent, boolean attachToParent) {
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ final View view = inflater.inflate(layoutId, parent, attachToParent);
+ return bindTo(view, layoutId);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T extends ViewDataBinding> T bindTo(View root, int layoutId) {
+ return (T) sMapper.getDataBinder(root, layoutId);
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ListChangeRegistry.java b/tools/data-binding/library/src/main/java/android/databinding/ListChangeRegistry.java
new file mode 100644
index 0000000..e4c65bd
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ListChangeRegistry.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+import android.support.v4.util.Pools;
+
+public class ListChangeRegistry
+ extends
+ CallbackRegistry<OnListChangedListener, ObservableList, ListChangeRegistry.ListChanges> {
+ private static final Pools.SynchronizedPool<ListChanges> sListChanges =
+ new Pools.SynchronizedPool<>(10);
+
+ private static final int ALL = 0;
+ private static final int CHANGED = 1;
+ private static final int INSERTED = 2;
+ private static final int MOVED = 3;
+ private static final int REMOVED = 4;
+
+ private static final CallbackRegistry.NotifierCallback<OnListChangedListener, ObservableList, ListChanges> NOTIFIER_CALLBACK = new CallbackRegistry.NotifierCallback<OnListChangedListener, ObservableList, ListChanges>() {
+ @Override
+ public void onNotifyCallback(OnListChangedListener callback, ObservableList sender,
+ int notificationType, ListChanges listChanges) {
+ switch (notificationType) {
+ case CHANGED:
+ callback.onItemRangeChanged(listChanges.start, listChanges.count);
+ break;
+ case INSERTED:
+ callback.onItemRangeInserted(listChanges.start, listChanges.count);
+ break;
+ case MOVED:
+ callback.onItemRangeMoved(listChanges.start, listChanges.to, listChanges.count);
+ break;
+ case REMOVED:
+ callback.onItemRangeRemoved(listChanges.start, listChanges.count);
+ break;
+ default:
+ callback.onChanged();
+ break;
+ }
+ if (listChanges != null) {
+ sListChanges.release(listChanges);
+ }
+ }
+ };
+
+ public void notifyChanged(ObservableList list) {
+ notifyCallbacks(list, ALL, null);
+ }
+
+ public void notifyChanged(ObservableList list, int start, int count) {
+ ListChanges listChanges = acquire(start, 0, count);
+ notifyCallbacks(list, CHANGED, listChanges);
+ }
+
+ public void notifyInserted(ObservableList list, int start, int count) {
+ ListChanges listChanges = acquire(start, 0, count);
+ notifyCallbacks(list, INSERTED, listChanges);
+ }
+
+ public void notifyMoved(ObservableList list, int from, int to, int count) {
+ ListChanges listChanges = acquire(from, to, count);
+ notifyCallbacks(list, MOVED, listChanges);
+ }
+
+ public void notifyRemoved(ObservableList list, int start, int count) {
+ ListChanges listChanges = acquire(start, 0, count);
+ notifyCallbacks(list, REMOVED, listChanges);
+ }
+
+ private static ListChanges acquire(int start, int to, int count) {
+ ListChanges listChanges = sListChanges.acquire();
+ if (listChanges == null) {
+ listChanges = new ListChanges();
+ }
+ listChanges.start = start;
+ listChanges.to = to;
+ listChanges.count = count;
+ return listChanges;
+ }
+
+ /**
+ * Creates an EventRegistry that notifies the event with notifier.
+ */
+ public ListChangeRegistry() {
+ super(NOTIFIER_CALLBACK);
+ }
+
+ static class ListChanges {
+ public int start;
+ public int count;
+ public int to;
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/MapChangeRegistry.java b/tools/data-binding/library/src/main/java/android/databinding/MapChangeRegistry.java
new file mode 100644
index 0000000..6403cce
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/MapChangeRegistry.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+public class MapChangeRegistry
+ extends CallbackRegistry<OnMapChangedListener, ObservableMap, Object> {
+
+ private static NotifierCallback<OnMapChangedListener, ObservableMap, Object> NOTIFIER_CALLBACK =
+ new NotifierCallback<OnMapChangedListener, ObservableMap, Object>() {
+ @Override
+ public void onNotifyCallback(OnMapChangedListener callback, ObservableMap sender,
+ int arg, Object arg2) {
+ callback.onMapChanged(sender, arg2);
+ }
+ };
+
+ public MapChangeRegistry() {
+ super(NOTIFIER_CALLBACK);
+ }
+
+ public void notifyChange(ObservableMap sender, Object key) {
+ notifyCallbacks(sender, 0, key);
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ObservableArrayList.java b/tools/data-binding/library/src/main/java/android/databinding/ObservableArrayList.java
new file mode 100644
index 0000000..d70bc68
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ObservableArrayList.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class ObservableArrayList<T> extends ArrayList<T> implements ObservableList<T> {
+ private ListChangeRegistry mListeners = new ListChangeRegistry();
+
+ @Override
+ public void addOnListChangedListener(OnListChangedListener listener) {
+ if (mListeners == null) {
+ mListeners = new ListChangeRegistry();
+ }
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeOnListChangedListener(OnListChangedListener listener) {
+ if (mListeners != null) {
+ mListeners.remove(listener);
+ }
+ }
+
+ @Override
+ public boolean add(T object) {
+ super.add(object);
+ notifyAdd(size() - 1, 1);
+ return true;
+ }
+
+ @Override
+ public void add(int index, T object) {
+ super.add(index, object);
+ notifyAdd(index, 1);
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends T> collection) {
+ int oldSize = size();
+ boolean added = super.addAll(collection);
+ if (added) {
+ notifyAdd(oldSize, size() - oldSize);
+ }
+ return added;
+ }
+
+ @Override
+ public boolean addAll(int index, Collection<? extends T> collection) {
+ boolean added = super.addAll(index, collection);
+ if (added) {
+ notifyAdd(index, collection.size());
+ }
+ return added;
+ }
+
+ @Override
+ public void clear() {
+ int oldSize = size();
+ super.clear();
+ if (oldSize != 0) {
+ notifyRemove(0, oldSize);
+ }
+ }
+
+ @Override
+ public T remove(int index) {
+ T val = super.remove(index);
+ notifyRemove(index, 1);
+ return val;
+ }
+
+ @Override
+ public boolean remove(Object object) {
+ int index = indexOf(object);
+ if (index >= 0) {
+ remove(index);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public T set(int index, T object) {
+ T val = super.set(index, object);
+ if (mListeners != null) {
+ mListeners.notifyChanged(this, index, 1);
+ }
+ return val;
+ }
+
+ @Override
+ protected void removeRange(int fromIndex, int toIndex) {
+ super.removeRange(fromIndex, toIndex);
+ notifyRemove(fromIndex, toIndex - fromIndex);
+ }
+
+ private void notifyAdd(int start, int count) {
+ if (mListeners != null) {
+ mListeners.notifyInserted(this, start, count);
+ }
+ }
+
+ private void notifyRemove(int start, int count) {
+ if (mListeners != null) {
+ mListeners.notifyRemoved(this, start, count);
+ }
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ObservableArrayMap.java b/tools/data-binding/library/src/main/java/android/databinding/ObservableArrayMap.java
new file mode 100644
index 0000000..c8b57b7
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ObservableArrayMap.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+import android.support.v4.util.ArrayMap;
+
+import java.util.Collection;
+
+public class ObservableArrayMap<K, V> extends ArrayMap<K, V> implements ObservableMap<K, V> {
+
+ private MapChangeRegistry mListeners;
+
+ @Override
+ public void addOnMapChangedListener(
+ OnMapChangedListener<? extends ObservableMap<K, V>, K> listener) {
+ if (mListeners == null) {
+ mListeners = new MapChangeRegistry();
+ }
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeOnMapChangedListener(
+ OnMapChangedListener<? extends ObservableMap<K, V>, K> listener) {
+ if (mListeners != null) {
+ mListeners.remove(listener);
+ }
+ }
+
+ @Override
+ public void clear() {
+ boolean wasEmpty = isEmpty();
+ if (!wasEmpty) {
+ super.clear();
+ notifyChange(null);
+ }
+ }
+
+ public V put(K k, V v) {
+ V val = super.put(k, v);
+ notifyChange(k);
+ return v;
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> collection) {
+ boolean removed = false;
+ for (Object key : collection) {
+ int index = indexOfKey(key);
+ if (index >= 0) {
+ removed = true;
+ removeAt(index);
+ }
+ }
+ return removed;
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> collection) {
+ boolean removed = false;
+ for (int i = size() - 1; i >= 0; i--) {
+ Object key = keyAt(i);
+ if (!collection.contains(key)) {
+ removeAt(i);
+ removed = true;
+ }
+ }
+ return removed;
+ }
+
+ @Override
+ public V removeAt(int index) {
+ K key = keyAt(index);
+ V value = super.removeAt(index);
+ if (value != null) {
+ notifyChange(key);
+ }
+ return value;
+ }
+
+ @Override
+ public V setValueAt(int index, V value) {
+ K key = keyAt(index);
+ V oldValue = super.setValueAt(index, value);
+ notifyChange(key);
+ return oldValue;
+ }
+
+ private void notifyChange(Object key) {
+ if (mListeners != null) {
+ mListeners.notifyCallbacks(this, 0, key);
+ }
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ObservableBoolean.java b/tools/data-binding/library/src/main/java/android/databinding/ObservableBoolean.java
new file mode 100644
index 0000000..afead95
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ObservableBoolean.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+public class ObservableBoolean extends BaseObservable {
+ private boolean mValue;
+
+ public boolean get() {
+ return mValue;
+ }
+
+ public void set(boolean value) {
+ mValue = value;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ObservableByte.java b/tools/data-binding/library/src/main/java/android/databinding/ObservableByte.java
new file mode 100644
index 0000000..3ee73a5
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ObservableByte.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+public class ObservableByte extends BaseObservable {
+ private byte mValue;
+
+ public byte get() {
+ return mValue;
+ }
+
+ public void set(byte value) {
+ mValue = value;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ObservableChar.java b/tools/data-binding/library/src/main/java/android/databinding/ObservableChar.java
new file mode 100644
index 0000000..f78231c
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ObservableChar.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+public class ObservableChar extends BaseObservable {
+ private char mValue;
+
+ public char get() {
+ return mValue;
+ }
+
+ public void set(char value) {
+ mValue = value;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ObservableDouble.java b/tools/data-binding/library/src/main/java/android/databinding/ObservableDouble.java
new file mode 100644
index 0000000..ee7bd05
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ObservableDouble.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+public class ObservableDouble extends BaseObservable {
+ private double mValue;
+
+ public double get() {
+ return mValue;
+ }
+
+ public void set(double value) {
+ mValue = value;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ObservableField.java b/tools/data-binding/library/src/main/java/android/databinding/ObservableField.java
new file mode 100644
index 0000000..05a61b7
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ObservableField.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+public class ObservableField<T> extends BaseObservable {
+ private T mValue;
+
+ public T get() {
+ return mValue;
+ }
+
+ public void set(T value) {
+ mValue = value;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ObservableFloat.java b/tools/data-binding/library/src/main/java/android/databinding/ObservableFloat.java
new file mode 100644
index 0000000..e8a063a
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ObservableFloat.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+public class ObservableFloat extends BaseObservable {
+ private float mValue;
+
+ public float get() {
+ return mValue;
+ }
+
+ public void set(float value) {
+ mValue = value;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ObservableInt.java b/tools/data-binding/library/src/main/java/android/databinding/ObservableInt.java
new file mode 100644
index 0000000..77140f8
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ObservableInt.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+public class ObservableInt extends BaseObservable {
+ private int mValue;
+
+ public int get() {
+ return mValue;
+ }
+
+ public void set(int value) {
+ mValue = value;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ObservableLong.java b/tools/data-binding/library/src/main/java/android/databinding/ObservableLong.java
new file mode 100644
index 0000000..b1621e1
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ObservableLong.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+public class ObservableLong extends BaseObservable {
+ private long mValue;
+
+ public long get() {
+ return mValue;
+ }
+
+ public void set(long value) {
+ mValue = value;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ObservableShort.java b/tools/data-binding/library/src/main/java/android/databinding/ObservableShort.java
new file mode 100644
index 0000000..6d46705
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ObservableShort.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+public class ObservableShort extends BaseObservable {
+ private short mValue;
+
+ public short get() {
+ return mValue;
+ }
+
+ public void set(short value) {
+ mValue = value;
+ notifyChange();
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/PropertyChangeRegistry.java b/tools/data-binding/library/src/main/java/android/databinding/PropertyChangeRegistry.java
new file mode 100644
index 0000000..c675c39
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/PropertyChangeRegistry.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+public class PropertyChangeRegistry extends
+ CallbackRegistry<OnPropertyChangedListener, Observable, Void> {
+
+ private static final CallbackRegistry.NotifierCallback<OnPropertyChangedListener, Observable, Void> NOTIFIER_CALLBACK = new CallbackRegistry.NotifierCallback<OnPropertyChangedListener, Observable, Void>() {
+ @Override
+ public void onNotifyCallback(OnPropertyChangedListener callback, Observable sender,
+ int arg, Void notUsed) {
+ callback.onPropertyChanged(sender, arg);
+ }
+ };
+
+ public PropertyChangeRegistry() {
+ super(NOTIFIER_CALLBACK);
+ }
+
+ public void notifyChange(Observable observable, int propertyId) {
+ notifyCallbacks(observable, propertyId, null);
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ViewDataBinding.java b/tools/data-binding/library/src/main/java/android/databinding/ViewDataBinding.java
new file mode 100644
index 0000000..a5ba6e3
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ViewDataBinding.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding;
+
+import com.android.databinding.library.R;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.ViewGroup;
+
+import java.lang.ref.WeakReference;
+
+public abstract class ViewDataBinding {
+
+ /**
+ * Instead of directly accessing Build.VERSION.SDK_INT, generated code uses this value so that
+ * we can test API dependent behavior.
+ */
+ static int SDK_INT = VERSION.SDK_INT;
+
+ /**
+ * Prefix for android:tag on Views with binding. The root View and include tags will not have
+ * android:tag attributes and will use ids instead.
+ */
+ public static final String BINDING_TAG_PREFIX = "bindingTag";
+
+ // The length of BINDING_TAG_PREFIX prevents calling length repeatedly.
+ private static final int BINDING_NUMBER_START = BINDING_TAG_PREFIX.length();
+
+ // ICS (v 14) fixes a leak when using setTag(int, Object)
+ private static final boolean USE_TAG_ID = DataBinderMapper.TARGET_MIN_SDK >= 14;
+
+ /**
+ * Method object extracted out to attach a listener to a bound Observable object.
+ */
+ private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
+ @Override
+ public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
+ return new WeakPropertyListener(viewDataBinding, localFieldId);
+ }
+ };
+
+ /**
+ * Method object extracted out to attach a listener to a bound ObservableList object.
+ */
+ private static final CreateWeakListener CREATE_LIST_LISTENER = new CreateWeakListener() {
+ @Override
+ public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
+ return new WeakListListener(viewDataBinding, localFieldId);
+ }
+ };
+
+ /**
+ * Method object extracted out to attach a listener to a bound ObservableMap object.
+ */
+ private static final CreateWeakListener CREATE_MAP_LISTENER = new CreateWeakListener() {
+ @Override
+ public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
+ return new WeakMapListener(viewDataBinding, localFieldId);
+ }
+ };
+
+ private static final OnAttachStateChangeListener ROOT_REATTACHED_LISTENER;
+
+ static {
+ if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
+ ROOT_REATTACHED_LISTENER = null;
+ } else {
+ ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
+ @TargetApi(VERSION_CODES.KITKAT)
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ // execute the pending bindings.
+ final ViewDataBinding binding;
+ if (USE_TAG_ID) {
+ binding = (ViewDataBinding) v.getTag(R.id.dataBinding);
+ } else {
+ binding = (ViewDataBinding) v.getTag();
+ }
+ v.post(binding.mRebindRunnable);
+ v.removeOnAttachStateChangeListener(this);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ };
+ }
+ }
+
+ /**
+ * Runnable executed on animation heartbeat to rebind the dirty Views.
+ */
+ private Runnable mRebindRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mPendingRebind) {
+ boolean rebind = true;
+ if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
+ rebind = mRoot.isAttachedToWindow();
+ if (!rebind) {
+ // Don't execute the pending bindings until the View
+ // is attached again.
+ mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
+ }
+ }
+ if (rebind) {
+ mPendingRebind = false;
+ executePendingBindings();
+ }
+ }
+ }
+ };
+
+ /**
+ * Flag indicates that there are pending bindings that need to be reevaluated.
+ */
+ private boolean mPendingRebind = false;
+
+ /**
+ * The observed expressions.
+ */
+ private WeakListener[] mLocalFieldObservers;
+
+ /**
+ * The root View that this Binding is associated with.
+ */
+ private final View mRoot;
+
+ protected ViewDataBinding(View root, int localFieldCount) {
+ mLocalFieldObservers = new WeakListener[localFieldCount];
+ this.mRoot = root;
+ if (USE_TAG_ID) {
+ this.mRoot.setTag(R.id.dataBinding, this);
+ } else {
+ this.mRoot.setTag(this);
+ }
+ }
+
+ public static int getBuildSdkInt() {
+ return SDK_INT;
+ }
+
+ /**
+ * Called when an observed object changes. Sets the appropriate dirty flag if applicable.
+ * @param localFieldId The index into mLocalFieldObservers that this Object resides in.
+ * @param object The object that has changed.
+ * @param fieldId The BR ID of the field being changed or _all if
+ * no specific field is being notified.
+ * @return true if this change should cause a change to the UI.
+ */
+ protected abstract boolean onFieldChange(int localFieldId, Object object, int fieldId);
+
+ public abstract boolean setVariable(int variableId, Object variable);
+
+ /**
+ * Evaluates the pending bindings, updating any Views that have expressions bound to
+ * modified variables. This <b>must</b> be run on the UI thread.
+ */
+ public abstract void executePendingBindings();
+
+ /**
+ * Used internally to invalidate flags of included layouts.
+ */
+ public abstract void invalidateAll();
+
+ /**
+ * Removes binding listeners to expression variables.
+ */
+ public void unbind() {
+ for (WeakListener weakListener : mLocalFieldObservers) {
+ if (weakListener != null) {
+ weakListener.unregister();
+ }
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ unbind();
+ }
+
+ /**
+ * Returns the outermost View in the layout file associated with the Binding.
+ * @return the outermost View in the layout file associated with the Binding.
+ */
+ public View getRoot() {
+ return mRoot;
+ }
+
+ private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
+ boolean result = onFieldChange(mLocalFieldId, object, fieldId);
+ if (result) {
+ requestRebind();
+ }
+ }
+
+ protected boolean unregisterFrom(int localFieldId) {
+ WeakListener listener = mLocalFieldObservers[localFieldId];
+ if (listener != null) {
+ return listener.unregister();
+ }
+ return false;
+ }
+
+ protected void requestRebind() {
+ if (mPendingRebind) {
+ return;
+ }
+ mPendingRebind = true;
+ if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
+ mRoot.postOnAnimation(mRebindRunnable);
+ } else {
+ mRoot.post(mRebindRunnable);
+ }
+ }
+
+ protected Object getObservedField(int localFieldId) {
+ WeakListener listener = mLocalFieldObservers[localFieldId];
+ if (listener == null) {
+ return null;
+ }
+ return listener.getTarget();
+ }
+
+ private boolean updateRegistration(int localFieldId, Object observable,
+ CreateWeakListener listenerCreator) {
+ if (observable == null) {
+ return unregisterFrom(localFieldId);
+ }
+ WeakListener listener = mLocalFieldObservers[localFieldId];
+ if (listener == null) {
+ registerTo(localFieldId, observable, listenerCreator);
+ return true;
+ }
+ if (listener.getTarget() == observable) {
+ return false;//nothing to do, same object
+ }
+ unregisterFrom(localFieldId);
+ registerTo(localFieldId, observable, listenerCreator);
+ return true;
+ }
+
+ protected boolean updateRegistration(int localFieldId, Observable observable) {
+ return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
+ }
+
+ protected boolean updateRegistration(int localFieldId, ObservableList observable) {
+ return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
+ }
+
+ protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
+ return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
+ }
+
+ protected void registerTo(int localFieldId, Object observable,
+ CreateWeakListener listenerCreator) {
+ if (observable == null) {
+ return;
+ }
+ WeakListener listener = mLocalFieldObservers[localFieldId];
+ if (listener == null) {
+ listener = listenerCreator.create(this, localFieldId);
+ mLocalFieldObservers[localFieldId] = listener;
+ }
+ listener.setTarget(observable);
+ }
+
+ /**
+ * Walk all children of root and assign tagged Views to associated indices in views.
+ *
+ * @param root The base of the View hierarchy to walk.
+ * @param views An array of all Views with binding expressions and all Views with IDs. This
+ * will start empty and will contain the found Views when this method completes.
+ * @param includes A mapping of include tag IDs to the index into the views array.
+ * @param viewsWithIds A mapping of views with IDs but without expressions to the index
+ * into the views array.
+ */
+ private static void mapTaggedChildViews(View root, View[] views, SparseIntArray includes,
+ SparseIntArray viewsWithIds) {
+ if (root instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) root;
+ for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
+ mapTaggedViews(viewGroup.getChildAt(i), views, includes, viewsWithIds);
+ }
+ }
+ }
+
+ private static void mapTaggedViews(View view, View[] views, SparseIntArray includes,
+ SparseIntArray viewsWithIds) {
+ boolean visitChildren = true;
+ String tag = (String) view.getTag();
+ if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
+ int tagIndex = parseTagInt(tag);
+ views[tagIndex] = view;
+ } else {
+ visitChildren = addViewWithId(view, views, includes, viewsWithIds);
+ }
+ if (visitChildren) {
+ mapTaggedChildViews(view, views, includes, viewsWithIds);
+ }
+ }
+
+ /**
+ * Walks the view hierarchy under root and pulls out tagged Views, includes, and views with
+ * IDs into a View[] that is returned. This is used to walk the view hierarchy once to find
+ * all bound and ID'd views.
+ *
+ * @param root The root of the view hierarchy to walk.
+ * @param numViews The total number of ID'd views and views with expressions.
+ * @param includes Views that are considered includes and should be treated as separate
+ * binders.
+ * @param viewsWithIds Views that don't have tags, but have IDs.
+ * @return An array of size numViews containing all Views in the hierarchy that have IDs
+ * (with elements in viewsWithIds) or are tagged containing expressions.
+ */
+ protected static View[] mapChildViews(View root, int numViews, SparseIntArray includes,
+ SparseIntArray viewsWithIds) {
+ View[] views = new View[numViews];
+ boolean visitChildren = addViewWithId(root, views, includes, viewsWithIds);
+ if (visitChildren) {
+ mapTaggedChildViews(root, views, includes, viewsWithIds);
+ }
+ return views;
+ }
+
+ private static boolean addViewWithId(View view, View[] views, SparseIntArray includes,
+ SparseIntArray viewsWithIds) {
+ final int id = view.getId();
+ boolean visitChildren = true;
+ if (id > 0) {
+ int index;
+ if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0) {
+ views[index] = view;
+ } else if (includes != null && (index = includes.get(id, -1)) >= 0) {
+ views[index] = view;
+ visitChildren = false;
+ }
+ }
+ return visitChildren;
+ }
+
+ /**
+ * Parse the tag without creating a new String object. This is fast and assumes the
+ * tag is in the correct format.
+ * @param str The tag string.
+ * @return The binding tag number parsed from the tag string.
+ */
+ private static int parseTagInt(String str) {
+ final int end = str.length();
+ int val = 0;
+ for (int i = BINDING_NUMBER_START; i < end; i++) {
+ val *= 10;
+ char c = str.charAt(i);
+ val += (c - '0');
+ }
+ return val;
+ }
+
+ private static abstract class WeakListener<T> {
+ private final WeakReference<ViewDataBinding> mBinder;
+ protected final int mLocalFieldId;
+ private T mTarget;
+
+ public WeakListener(ViewDataBinding binder, int localFieldId) {
+ mBinder = new WeakReference<ViewDataBinding>(binder);
+ mLocalFieldId = localFieldId;
+ }
+
+ public void setTarget(T object) {
+ unregister();
+ mTarget = object;
+ if (mTarget != null) {
+ addListener(mTarget);
+ }
+ }
+
+ public boolean unregister() {
+ boolean unregistered = false;
+ if (mTarget != null) {
+ removeListener(mTarget);
+ unregistered = true;
+ }
+ mTarget = null;
+ return unregistered;
+ }
+
+ public T getTarget() {
+ return mTarget;
+ }
+
+ protected ViewDataBinding getBinder() {
+ ViewDataBinding binder = mBinder.get();
+ if (binder == null) {
+ unregister(); // The binder is dead
+ }
+ return binder;
+ }
+
+ protected abstract void addListener(T target);
+ protected abstract void removeListener(T target);
+ }
+
+ private static class WeakPropertyListener extends WeakListener<Observable>
+ implements OnPropertyChangedListener {
+ public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
+ super(binder, localFieldId);
+ }
+
+ @Override
+ protected void addListener(Observable target) {
+ target.addOnPropertyChangedListener(this);
+ }
+
+ @Override
+ protected void removeListener(Observable target) {
+ target.removeOnPropertyChangedListener(this);
+ }
+
+ @Override
+ public void onPropertyChanged(Observable sender, int fieldId) {
+ ViewDataBinding binder = getBinder();
+ if (binder == null) {
+ return;
+ }
+ Observable obj = getTarget();
+ if (obj != sender) {
+ return; // notification from the wrong object?
+ }
+ binder.handleFieldChange(mLocalFieldId, sender, fieldId);
+ }
+ }
+
+ private static class WeakListListener extends WeakListener<ObservableList>
+ implements OnListChangedListener {
+
+ public WeakListListener(ViewDataBinding binder, int localFieldId) {
+ super(binder, localFieldId);
+ }
+
+ @Override
+ public void onChanged() {
+ ViewDataBinding binder = getBinder();
+ if (binder == null) {
+ return;
+ }
+ ObservableList target = getTarget();
+ if (target == null) {
+ return; // We don't expect any notifications from null targets
+ }
+ binder.handleFieldChange(mLocalFieldId, target, 0);
+ }
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ onChanged();
+ }
+
+ @Override
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ onChanged();
+ }
+
+ @Override
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ onChanged();
+ }
+
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ onChanged();
+ }
+
+ @Override
+ protected void addListener(ObservableList target) {
+ target.addOnListChangedListener(this);
+ }
+
+ @Override
+ protected void removeListener(ObservableList target) {
+ target.removeOnListChangedListener(this);
+ }
+ }
+
+ private static class WeakMapListener extends WeakListener<ObservableMap>
+ implements OnMapChangedListener {
+ public WeakMapListener(ViewDataBinding binder, int localFieldId) {
+ super(binder, localFieldId);
+ }
+
+ @Override
+ protected void addListener(ObservableMap target) {
+ target.addOnMapChangedListener(this);
+ }
+
+ @Override
+ protected void removeListener(ObservableMap target) {
+ target.removeOnMapChangedListener(this);
+ }
+
+ @Override
+ public void onMapChanged(ObservableMap sender, Object key) {
+ ViewDataBinding binder = getBinder();
+ if (binder == null || sender != getTarget()) {
+ return;
+ }
+ binder.handleFieldChange(mLocalFieldId, sender, 0);
+ }
+ }
+
+ private interface CreateWeakListener {
+ WeakListener create(ViewDataBinding viewDataBinding, int localFieldId);
+ }
+}
diff --git a/tools/data-binding/library/src/main/java/android/databinding/ViewStubProxy.java b/tools/data-binding/library/src/main/java/android/databinding/ViewStubProxy.java
new file mode 100644
index 0000000..01b878a
--- /dev/null
+++ b/tools/data-binding/library/src/main/java/android/databinding/ViewStubProxy.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding;
+
+import android.view.View;
+import android.view.ViewStub;
+import android.view.ViewStub.OnInflateListener;
+
+/**
+ * This class represents a ViewStub before and after inflation. Before inflation,
+ * the ViewStub is accessible. After inflation, the ViewDataBinding is accessible
+ * if the inflated View has bindings. If not, the root View will be accessible.
+ */
+public class ViewStubProxy {
+ private ViewStub mViewStub;
+ private ViewDataBinding mViewDataBinding;
+ private View mRoot;
+ private OnInflateListener mOnInflateListener;
+ private ViewDataBinding mContainingBinding;
+
+ private OnInflateListener mProxyListener = new OnInflateListener() {
+ @Override
+ public void onInflate(ViewStub stub, View inflated) {
+ mRoot = inflated;
+ mViewDataBinding = DataBindingUtil.bindTo(inflated, stub.getLayoutResource());
+ mViewStub = null;
+
+ if (mOnInflateListener != null) {
+ mOnInflateListener.onInflate(stub, inflated);
+ mOnInflateListener = null;
+ }
+ mContainingBinding.invalidateAll();
+ mContainingBinding.executePendingBindings();
+ }
+ };
+
+ public ViewStubProxy(ViewStub viewStub) {
+ mViewStub = viewStub;
+ mViewStub.setOnInflateListener(mProxyListener);
+ }
+
+ public void setContainingBinding(ViewDataBinding containingBinding) {
+ mContainingBinding = containingBinding;
+ }
+
+ /**
+ * @return <code>true</code> if the ViewStub has replaced itself with the inflated layout
+ * or <code>false</code> if not.
+ */
+ public boolean isInflated() {
+ return mRoot != null;
+ }
+
+ /**
+ * @return The root View of the layout replacing the ViewStub once it has been inflated.
+ * <code>null</code> is returned prior to inflation.
+ */
+ public View getRoot() {
+ return mRoot;
+ }
+
+ /**
+ * @return The data binding associated with the inflated layout once it has been inflated.
+ * <code>null</code> prior to inflation or if there is no binding associated with the layout.
+ */
+ public ViewDataBinding getBinding() {
+ return mViewDataBinding;
+ }
+
+ /**
+ * @return The ViewStub in the layout or <code>null</code> if the ViewStub has been inflated.
+ */
+ public ViewStub getViewStub() {
+ return mViewStub;
+ }
+
+ /**
+ * Sets the {@link OnInflateListener} to be called when the ViewStub inflates. The proxy must
+ * have an OnInflateListener, so <code>listener</code> will be called immediately after
+ * the proxy's listener is called.
+ *
+ * @param listener The OnInflateListener to notify of successful inflation
+ */
+ public void setOnInflateListener(OnInflateListener listener) {
+ if (mViewStub != null) {
+ mOnInflateListener = listener;
+ }
+ }
+}
diff --git a/tools/data-binding/library/src/main/res/values/ids.xml b/tools/data-binding/library/src/main/res/values/ids.xml
new file mode 100644
index 0000000..a7b89d3
--- /dev/null
+++ b/tools/data-binding/library/src/main/res/values/ids.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <item type="id" name="dataBinding"/>
+</resources>
\ No newline at end of file
diff --git a/tools/data-binding/samples/BindingDemo/.gitignore b/tools/data-binding/samples/BindingDemo/.gitignore
new file mode 100644
index 0000000..afbdab3
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/.gitignore
@@ -0,0 +1,6 @@
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
diff --git a/tools/data-binding/samples/BindingDemo/app/.gitignore b/tools/data-binding/samples/BindingDemo/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/tools/data-binding/samples/BindingDemo/app/build.gradle b/tools/data-binding/samples/BindingDemo/app/build.gradle
new file mode 100644
index 0000000..fa54aab
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/build.gradle
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+apply plugin: 'com.android.application'
+apply plugin: 'com.android.databinding'
+
+def generatedSources = "$buildDir/generated/source/br"
+
+android {
+ compileSdkVersion 21
+ buildToolsVersion "21.1.1"
+
+ defaultConfig {
+ applicationId "com.android.bindingdemo"
+ minSdkVersion 15
+ targetSdkVersion 21
+ versionCode 1
+ versionName "1.0"
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ packagingOptions {
+ exclude 'META-INF/services/javax.annotation.processing.Processor'
+ }
+}
+
+android.applicationVariants.all { variant ->
+ variant.javaCompile.doFirst {
+ println "*** compile doFirst ${variant.name}"
+ new File(generatedSources).mkdirs()
+ variant.javaCompile.options.compilerArgs += [
+ '-s', generatedSources
+ ]
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile 'com.android.support:appcompat-v7:21.+'
+ compile 'com.android.databinding:library:0.3-SNAPSHOT@aar'
+ compile 'com.android.support:recyclerview-v7:21.0.2'
+ compile 'com.android.support:gridlayout-v7:21.+'
+ compile 'com.android.support:cardview-v7:21.+'
+ compile 'com.android.databinding:baseLibrary:0.3-SNAPSHOT'
+ compile 'com.android.databinding:adapters:0.3-SNAPSHOT'
+ provided 'com.android.databinding:annotationprocessor:0.3-SNAPSHOT'
+ provided fileTree(dir : 'build/databinder/src', include : ['*.java'])
+
+ testCompile 'junit:junit:4.12'
+ testCompile 'org.mockito:mockito-core:1.9.5'
+}
diff --git a/tools/data-binding/samples/BindingDemo/app/proguard-rules.pro b/tools/data-binding/samples/BindingDemo/app/proguard-rules.pro
new file mode 100644
index 0000000..b7210d1
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/yboyar/android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/AndroidManifest.xml b/tools/data-binding/samples/BindingDemo/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3455d7c
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.example.bindingdemo" >
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/DataBoundAdapter.java b/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/DataBoundAdapter.java
new file mode 100644
index 0000000..89eef68
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/DataBoundAdapter.java
@@ -0,0 +1,31 @@
+package com.android.example.bindingdemo;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.ViewGroup;
+
+import android.databinding.DataBindingUtil;
+import android.databinding.ViewDataBinding;
+
+abstract public class DataBoundAdapter<T extends ViewDataBinding>
+ extends RecyclerView.Adapter<DataBoundAdapter.DataBoundViewHolder<T>> {
+ final int mLayoutId;
+ final Class<T> mBinderInterface;
+ public DataBoundAdapter(int mLayoutId, Class<T> mBinderInterface) {
+ this.mLayoutId = mLayoutId;
+ this.mBinderInterface = mBinderInterface;
+ }
+
+ @Override
+ public DataBoundAdapter.DataBoundViewHolder<T> onCreateViewHolder(ViewGroup viewGroup, int type) {
+ T binder = DataBindingUtil.inflate(viewGroup.getContext(), mLayoutId, viewGroup, false);
+ return new DataBoundViewHolder(binder);
+ }
+
+ static class DataBoundViewHolder<T extends ViewDataBinding> extends RecyclerView.ViewHolder {
+ public final T dataBinder;
+ public DataBoundViewHolder(T mViewBinder) {
+ super(mViewBinder.getRoot());
+ this.dataBinder = mViewBinder;
+ }
+ }
+}
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/MainActivity.java b/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/MainActivity.java
new file mode 100644
index 0000000..bfd5e4a
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/MainActivity.java
@@ -0,0 +1,225 @@
+package com.android.example.bindingdemo;
+
+import android.databinding.Bindable;
+import android.databinding.Observable;
+import android.databinding.OnPropertyChangedListener;
+import android.support.v7.app.ActionBarActivity;
+import android.os.Bundle;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import android.databinding.DataBindingUtil;
+import android.databinding.PropertyChangeRegistry;
+import com.android.example.bindingdemo.databinding.ListItemBinding;
+import com.android.example.bindingdemo.databinding.MainActivityBinding;
+import com.android.example.bindingdemo.vo.User;
+import com.android.example.bindingdemo.vo.Users;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.android.example.bindingdemo.BR;
+public class MainActivity extends ActionBarActivity implements Observable {
+ @Bindable
+ UserAdapter tkAdapter;
+ @Bindable
+ UserAdapter robotAdapter;
+ @Bindable
+ MainActivityBinding dataBinder;
+ @Bindable
+ User selected;
+
+ @Bindable
+ User selected2;
+
+ private final PropertyChangeRegistry mListeners = new PropertyChangeRegistry();
+
+ public User getSelected2() {
+ return selected2;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ dataBinder = MainActivityBinding.inflate(this);
+ setContentView(dataBinder.getRoot());
+ dataBinder.robotList.setHasFixedSize(true);
+ dataBinder.toolkittyList.setHasFixedSize(true);
+ tkAdapter = new UserAdapter(Users.toolkities);
+ robotAdapter = new UserAdapter(Users.robots);
+ dataBinder.toolkittyList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
+ dataBinder.robotList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
+ dataBinder.setActivity(this);
+ dataBinder.executePendingBindings();
+ }
+
+ public UserAdapter getTkAdapter() {
+ return tkAdapter;
+ }
+
+ public UserAdapter getRobotAdapter() {
+ return robotAdapter;
+ }
+
+ public User getSelected() {
+ return selected;
+ }
+
+ private void setSelected(User selected) {
+ if (selected == this.selected) {
+ return;
+ }
+ this.selected = selected;
+ mListeners.notifyChange(this, BR.selected);
+ }
+
+ @Bindable
+ public View.OnClickListener onSave = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (selected == null) {
+ return;
+ }
+ selected.setName(dataBinder.selectedName.getText().toString());
+ selected.setLastName(dataBinder.selectedLastname.getText().toString());
+ }
+ };
+
+ @Bindable
+ public View.OnClickListener onUnselect = new View.OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ setSelected(null);
+ }
+ };
+
+ @Bindable
+ public View.OnClickListener onDelete = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (selected == null) {
+ return;
+ }
+ if (selected.getGroup() == User.TOOLKITTY) {
+ tkAdapter.remove(selected);
+ selected.setGroup(User.ROBOT);
+ robotAdapter.add(selected);
+ dataBinder.robotList.smoothScrollToPosition(robotAdapter.getItemCount() - 1);
+ } else {
+ tkAdapter.add(selected);
+ dataBinder.toolkittyList.smoothScrollToPosition(tkAdapter.getItemCount() - 1);
+ selected.setGroup(User.TOOLKITTY);
+ robotAdapter.remove(selected);
+ }
+ }
+ };
+
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void addOnPropertyChangedListener(OnPropertyChangedListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeOnPropertyChangedListener(OnPropertyChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ public class UserAdapter extends DataBoundAdapter<ListItemBinding> implements View.OnClickListener, Observable {
+ final private List<User> userList = new ArrayList<>();
+ final private PropertyChangeRegistry mListeners = new PropertyChangeRegistry();
+
+ public UserAdapter(User[] toolkities) {
+ super(R.layout.list_item, ListItemBinding.class);
+ userList.addAll(Arrays.asList(toolkities));
+ }
+
+ @Override
+ public DataBoundViewHolder<ListItemBinding> onCreateViewHolder(ViewGroup viewGroup, int type) {
+ DataBoundViewHolder<ListItemBinding> vh = super.onCreateViewHolder(viewGroup, type);
+ vh.dataBinder.setClickListener(this);
+ return vh;
+ }
+
+ @Override
+ public void onBindViewHolder(DataBoundViewHolder<ListItemBinding> vh, int index) {
+ vh.dataBinder.setUser(userList.get(index));
+ vh.dataBinder.executePendingBindings();
+ }
+
+ @Bindable
+ @Override
+ public int getItemCount() {
+ return userList.size();
+ }
+
+ public void add(User user) {
+ if (userList.contains(user)) {
+ return;
+ }
+ userList.add(user);
+ notifyItemInserted(userList.size() - 1);
+ mListeners.notifyChange(this, BR.itemCount);
+ }
+
+ public void remove(User user) {
+ int i = userList.indexOf(user);
+ if (i < 0) {
+ return;
+ }
+ userList.remove(i);
+ notifyItemRemoved(i);
+ mListeners.notifyChange(this, BR.itemCount);
+ }
+
+ @Override
+ public void onClick(View v) {
+ RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) v.getLayoutParams();
+ final int pos = lp.getViewPosition();
+ if (pos > -1 && pos < userList.size()) {
+ v.requestFocus();
+ setSelected(userList.get(pos));
+ } else {
+ setSelected(null);
+ }
+ }
+
+ @Override
+ public void addOnPropertyChangedListener(OnPropertyChangedListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void removeOnPropertyChangedListener(OnPropertyChangedListener listener) {
+ mListeners.remove(listener);
+ }
+ }
+}
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/vo/User.java b/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/vo/User.java
new file mode 100644
index 0000000..5b7e5c7
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/vo/User.java
@@ -0,0 +1,83 @@
+package com.android.example.bindingdemo.vo;
+
+import android.databinding.Bindable;
+import android.graphics.Color;
+
+import android.databinding.BaseObservable;
+import com.android.example.bindingdemo.BR;
+
+import java.util.Objects;
+
+public class User extends BaseObservable {
+ @Bindable
+ private String name;
+ @Bindable
+ private String lastName;
+ @Bindable
+ private int photoResource = 0;
+ @Bindable
+ private int favoriteColor = Color.RED;
+ @Bindable
+ private int group;
+ public static final int TOOLKITTY = 1;
+ public static final int ROBOT = 2;
+
+ public User(String name, String lastName, int photoResource, int group) {
+ this.name = name;
+ this.lastName = lastName;
+ this.photoResource = photoResource;
+ this.group = group;
+ }
+
+ public void setGroup(int group) {
+ if (this.group == group) {
+ return;
+ }
+ this.group = group;
+ notifyPropertyChanged(BR.group);
+ }
+
+ public int getGroup() {
+ return group;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ if (Objects.equals(name, this.name)) {
+ return;
+ }
+ this.name = name;
+ notifyPropertyChanged(BR.name);
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ if (Objects.equals(lastName, this.lastName)) {
+ return;
+ }
+ this.lastName = lastName;
+ notifyPropertyChanged(BR.lastName);
+ }
+
+ public int getPhotoResource() {
+ return photoResource;
+ }
+
+ public void setPhotoResource(int photoResource) {
+ if (this.photoResource == photoResource) {
+ return;
+ }
+ this.photoResource = photoResource;
+ notifyPropertyChanged(BR.photoResource);
+ }
+
+ public int getFavoriteColor() {
+ return favoriteColor;
+ }
+}
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/vo/Users.java b/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/vo/Users.java
new file mode 100644
index 0000000..d954888
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/java/com/android/example/bindingdemo/vo/Users.java
@@ -0,0 +1,22 @@
+package com.android.example.bindingdemo.vo;
+
+import com.android.example.bindingdemo.R;
+
+public class Users {
+ public static final User[] robots = new User[]{
+ new User("romain", "guy", R.drawable.romain, User.ROBOT),
+ };
+ public static final User[] toolkities = new User[]{
+ new User("chet", "haase", R.drawable.chet, User.TOOLKITTY),
+ new User("adam", "powell", R.drawable.adam, User.TOOLKITTY),
+ new User("alan", "viverette", R.drawable.alan, User.TOOLKITTY),
+ new User("chris", "craik", R.drawable.chris, User.TOOLKITTY),
+ new User("george", "mount", R.drawable.george, User.TOOLKITTY),
+ new User("john", "reck", R.drawable.john, User.TOOLKITTY),
+ new User("rob", "tsuk", R.drawable.rob, User.TOOLKITTY),
+ new User("Teng-Hui", "Zhu", R.drawable.tenghui, User.TOOLKITTY),
+ new User("yigit", "boyar", R.drawable.yigit, User.TOOLKITTY),
+
+
+ };
+}
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable-hdpi/ic_launcher.png b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable-mdpi/ic_launcher.png b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable-xhdpi/ic_launcher.png b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4df1894
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/adam.png b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/adam.png
new file mode 100644
index 0000000..583a065
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/adam.png
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/alan.png b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/alan.png
new file mode 100644
index 0000000..c0c9161
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/alan.png
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/chet.png b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/chet.png
new file mode 100644
index 0000000..06cc751
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/chet.png
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/chris.png b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/chris.png
new file mode 100644
index 0000000..11686c5
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/chris.png
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/george.png b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/george.png
new file mode 100644
index 0000000..fe744e0
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/george.png
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/john.png b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/john.png
new file mode 100644
index 0000000..7bd0108
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/john.png
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/rob.png b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/rob.png
new file mode 100644
index 0000000..fd41cb07
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/rob.png
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/romain.png b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/romain.png
new file mode 100644
index 0000000..7a9af15
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/romain.png
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/tenghui.png b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/tenghui.png
new file mode 100644
index 0000000..13442b0
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/tenghui.png
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/yigit.png b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/yigit.png
new file mode 100644
index 0000000..57e9baf
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/drawable/yigit.png
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/layout/list_item.xml b/tools/data-binding/samples/BindingDemo/app/src/main/res/layout/list_item.xml
new file mode 100644
index 0000000..1aaf2f0
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/layout/list_item.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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.
+ -->
+
+<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:card_view="http://schemas.android.com/apk/res-auto"
+ xmlns:bind="http://schemas.android.com/apk/res-auto"
+ xmlns:bind_var="http://schemas.android.com/apk/res-auto"
+ card_view:cardUseCompatPadding="true"
+ card_view:contentPadding="8dp"
+ android:orientation="horizontal" android:layout_width="wrap_content"
+ android:id="@+id/root"
+ android:focusable="true"
+ android:gravity="center_vertical"
+ bind:onClickListener="@{clickListener}"
+ android:layout_height="match_parent">
+ <variable name="user" type="com.android.example.bindingdemo.vo.User"/>
+ <variable name="clickListener" type="android.view.View.OnClickListener"/>
+ <ImageView
+ android:id="@+id/user_photo"
+ android:backgroundResource="@{user.photoResource}"
+ android:scaleType="fitCenter"
+ android:layout_width="@dimen/user_photo"
+ android:layout_height="@dimen/user_photo" />
+
+ <TextView
+ android:layout_marginLeft="@dimen/user_name_margin_left"
+ android:id="@+id/fullname"
+ android:gravity="center"
+ android:text='@{user.name.substring(0,1).toUpperCase() + "." + user.lastName}'
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent" />
+</android.support.v7.widget.CardView>
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/layout/main_activity.xml b/tools/data-binding/samples/BindingDemo/app/src/main/res/layout/main_activity.xml
new file mode 100644
index 0000000..03d7368
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/layout/main_activity.xml
@@ -0,0 +1,141 @@
+<!--
+ Copyright (C) 2014 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:bind="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:id="@+id/activityRoot"
+ tools:activity=".MainActivity"
+ android:clickable="true"
+ android:onClickListener="@{activity.onUnselect}">
+ <variable name="activity" type="com.android.example.bindingdemo.MainActivity"/>
+ <!---->
+ <import
+ type="android.view.View"
+ />
+ <!---->
+ <import type="com.android.example.bindingdemo.R.string" alias="Strings"/>
+ <import type="com.android.example.bindingdemo.vo.User"/>
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{activity.getString(Strings.toolkitties, activity.tkAdapter.itemCount)}">
+
+ </TextView>
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/toolkittyList"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/list_height"
+ bind:adapter="@{activity.tkAdapter}"
+ ></android.support.v7.widget.RecyclerView>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="10dp" />
+
+ <TextView android:text="@{activity.selected2 == activity.selected ? `same` : `different`}"
+ android:id="@+id/deleteme"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ <android.support.v7.widget.CardView
+ android:id="@+id/selected_card"
+ bind:contentPadding="@{activity.selected == null ? 5 : activity.selected.name.length()}"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ bind:visibility="@{activity.selected == null ? View.INVISIBLE : View.VISIBLE}">
+
+ <GridLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:columnCount="2"
+ android:rowCount="4">
+
+ <ImageView
+ android:id="@+id/selected_photo"
+ android:layout_width="@dimen/big_user_photo"
+ android:layout_height="@dimen/big_user_photo"
+ android:layout_column="0"
+ android:layout_row="0"
+ android:layout_rowSpan="2"
+ android:scaleType="fitCenter"
+ android:backgroundResource="@{activity.selected.photoResource}" />
+
+ <EditText
+ android:id="@+id/selected_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_column="1"
+ android:layout_gravity="fill"
+ android:layout_row="0"
+ android:background="@android:color/holo_blue_dark"
+ android:gravity="center"
+ android:text="@{activity.selected.name}" />
+
+ <EditText
+ android:id="@+id/selected_lastname"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_column="1"
+ android:layout_gravity="fill"
+ android:layout_row="1"
+ android:background="@android:color/holo_blue_bright"
+ android:gravity="center"
+ android:text="@{activity.selected.lastName}" />
+ <Button
+ android:id="@+id/edit_button"
+ bind:onClickListener="@{activity.onSave}"
+ android:text="@{`Save changes to ` + activity.selected.name}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_column="1"
+ android:layout_gravity="right"
+ android:layout_row="2"/>
+
+ <Button
+ android:id="@+id/delete_button"
+ bind:onClickListener="@{activity.onDelete}"
+ android:text="@{activity.getString(activity.selected.group == User.TOOLKITTY ? Strings.became_robot : Strings.became_kitten, activity.selected.name)}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_column="1"
+ android:layout_gravity="right"
+ android:layout_row="3"/>
+
+ </GridLayout>
+ </android.support.v7.widget.CardView>
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="10dp" />
+ <TextView
+ android:id="@+id/robotsTitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{activity.robotAdapter.itemCount + " Robots "}" />
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/robotList"
+ android:layout_width="match_parent" bind:adapter="@{activity.robotAdapter}" android:layout_height="@dimen/list_height"
+ ></android.support.v7.widget.RecyclerView>
+</LinearLayout>
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/menu/menu_main.xml b/tools/data-binding/samples/BindingDemo/app/src/main/res/menu/menu_main.xml
new file mode 100644
index 0000000..ab38265
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/menu/menu_main.xml
@@ -0,0 +1,22 @@
+<!--
+ Copyright (C) 2014 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.
+ -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
+ <item android:id="@+id/action_settings" android:title="@string/action_settings"
+ android:orderInCategory="100" app:showAsAction="never" />
+</menu>
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/values-w820dp/dimens.xml b/tools/data-binding/samples/BindingDemo/app/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..793d18d
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,22 @@
+<!--
+ Copyright (C) 2014 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.
+ -->
+
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/values/dimens.xml b/tools/data-binding/samples/BindingDemo/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..7c360c6
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/values/dimens.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2014 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.
+ -->
+
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+ <dimen name="list_height">80dp</dimen>
+ <dimen name="user_photo">48dp</dimen>
+ <dimen name="user_name_margin_left">56dp</dimen>
+ <dimen name="big_user_photo">96dp</dimen>
+ <dimen name="item_width">200dp</dimen>
+</resources>
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/values/strings.xml b/tools/data-binding/samples/BindingDemo/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..ea6f485
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/values/strings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 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.
+ -->
+
+<resources>
+
+ <string name="app_name">BindingDemo</string>
+ <string name="hello_world">Hello world!</string>
+ <string name="action_settings">Settings</string>
+ <string name="toolkitty_list">toolkitties</string>
+ <string name="became_robot">%s joined robots</string>
+ <string name="became_kitten">%s joined tool kitties</string>
+ <string name="toolkitties">%s ToolKitties</string>
+
+</resources>
diff --git a/tools/data-binding/samples/BindingDemo/app/src/main/res/values/styles.xml b/tools/data-binding/samples/BindingDemo/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..00d89fa
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/main/res/values/styles.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright (C) 2014 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.
+ -->
+
+<resources>
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ </style>
+
+</resources>
diff --git a/tools/data-binding/samples/BindingDemo/app/src/test/java/com/android/example/bindingdemo/vo/UnitTest.java b/tools/data-binding/samples/BindingDemo/app/src/test/java/com/android/example/bindingdemo/vo/UnitTest.java
new file mode 100644
index 0000000..65449ce
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/app/src/test/java/com/android/example/bindingdemo/vo/UnitTest.java
@@ -0,0 +1,40 @@
+package com.android.example.bindingdemo.vo;
+
+import android.databinding.OnPropertyChangedListener;
+
+import com.android.example.bindingdemo.R;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import com.android.example.bindingdemo.BR;
+public class UnitTest {
+
+ private User testUser;
+
+ @Before
+ public void setUp() throws Exception {
+ testUser = new User("Ted", "Tester", R.drawable.george, User.ROBOT);
+ }
+
+ @Test
+ public void settersWorkFineOnTheJvm() throws Exception {
+ assertEquals("Ted", testUser.getName());
+ testUser.setName("Tom");
+ assertEquals("Tom", testUser.getName());
+ }
+
+ @Test
+ public void listeners() throws Exception {
+ OnPropertyChangedListener mockListener = mock(OnPropertyChangedListener.class);
+ testUser.addOnPropertyChangedListener(mockListener);
+ testUser.setName("Tom");
+ verify(mockListener).onPropertyChanged(testUser, BR.name);
+ verifyNoMoreInteractions(mockListener);
+ }
+}
diff --git a/tools/data-binding/samples/BindingDemo/build.gradle b/tools/data-binding/samples/BindingDemo/build.gradle
new file mode 100644
index 0000000..18f0f56
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/build.gradle
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ jcenter()
+ maven {
+ url "$projectDir/../../maven-repo"
+ }
+ mavenCentral()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:1.1.3"
+ classpath 'com.android.databinding:dataBinder:0.3-SNAPSHOT'
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ maven {
+ url "$projectDir/../../../maven-repo"
+ }
+ mavenCentral()
+ }
+}
diff --git a/tools/data-binding/samples/BindingDemo/gradle.properties b/tools/data-binding/samples/BindingDemo/gradle.properties
new file mode 100644
index 0000000..f60a634
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/gradle.properties
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2014 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.
+#
+
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/tools/data-binding/samples/BindingDemo/gradle/wrapper/gradle-wrapper.jar b/tools/data-binding/samples/BindingDemo/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/tools/data-binding/samples/BindingDemo/gradle/wrapper/gradle-wrapper.properties b/tools/data-binding/samples/BindingDemo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..f02c72a
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Dec 17 11:22:31 PST 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/tools/data-binding/samples/BindingDemo/gradlew b/tools/data-binding/samples/BindingDemo/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/tools/data-binding/samples/BindingDemo/gradlew.bat b/tools/data-binding/samples/BindingDemo/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/tools/data-binding/samples/BindingDemo/settings.gradle b/tools/data-binding/samples/BindingDemo/settings.gradle
new file mode 100644
index 0000000..c465666
--- /dev/null
+++ b/tools/data-binding/samples/BindingDemo/settings.gradle
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+include ':app'
diff --git a/tools/data-binding/settings.gradle b/tools/data-binding/settings.gradle
new file mode 100644
index 0000000..2190518
--- /dev/null
+++ b/tools/data-binding/settings.gradle
@@ -0,0 +1,7 @@
+include ':baseLibrary', ':app'
+include ':library'
+include ':compiler'
+include ':gradlePlugin'
+include ':grammarBuilder'
+include ':annotationprocessor'
+include ':xmlGrammar'
diff --git a/tools/data-binding/xmlGrammar/XMLLexer.g4 b/tools/data-binding/xmlGrammar/XMLLexer.g4
new file mode 100644
index 0000000..ea7a23c
--- /dev/null
+++ b/tools/data-binding/xmlGrammar/XMLLexer.g4
@@ -0,0 +1,93 @@
+/*
+ [The "BSD licence"]
+ Copyright (c) 2013 Terence Parr
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/** XML lexer derived from ANTLR v4 ref guide book example */
+lexer grammar XMLLexer;
+
+// Default "mode": Everything OUTSIDE of a tag
+COMMENT : '<!--' .*? '-->' ;
+CDATA : '<![CDATA[' .*? ']]>' ;
+/** Scarf all DTD stuff, Entity Declarations like <!ENTITY ...>,
+ * and Notation Declarations <!NOTATION ...>
+ */
+DTD : '<!' .*? '>' -> skip ;
+EntityRef : '&' Name ';' ;
+CharRef : '&#' DIGIT+ ';'
+ | '&#x' HEXDIGIT+ ';'
+ ;
+SEA_WS : (' '|'\t'|'\r'? '\n')+ ;
+
+OPEN : '<' -> pushMode(INSIDE) ;
+XMLDeclOpen : '<?xml' S -> pushMode(INSIDE) ;
+SPECIAL_OPEN: '<?' Name -> more, pushMode(PROC_INSTR) ;
+
+TEXT : ~[<&]+ ; // match any 16 bit char other than < and &
+
+// ----------------- Everything INSIDE of a tag ---------------------
+mode INSIDE;
+
+CLOSE : '>' -> popMode ;
+SPECIAL_CLOSE: '?>' -> popMode ; // close <?xml...?>
+SLASH_CLOSE : '/>' -> popMode ;
+SLASH : '/' ;
+EQUALS : '=' ;
+STRING : '"' ~[<"]* '"'
+ | '\'' ~[<']* '\''
+ ;
+Name : NameStartChar NameChar* ;
+S : [ \t\r\n] -> skip ;
+
+fragment
+HEXDIGIT : [a-fA-F0-9] ;
+
+fragment
+DIGIT : [0-9] ;
+
+fragment
+NameChar : NameStartChar
+ | '-' | '_' | '.' | DIGIT
+ | '\u00B7'
+ | '\u0300'..'\u036F'
+ | '\u203F'..'\u2040'
+ ;
+
+fragment
+NameStartChar
+ : [:a-zA-Z]
+ | '\u2070'..'\u218F'
+ | '\u2C00'..'\u2FEF'
+ | '\u3001'..'\uD7FF'
+ | '\uF900'..'\uFDCF'
+ | '\uFDF0'..'\uFFFD'
+ ;
+
+// ----------------- Handle <? ... ?> ---------------------
+mode PROC_INSTR;
+
+PI : '?>' -> popMode ; // close <?...?>
+IGNORE : . -> more ;
diff --git a/tools/data-binding/xmlGrammar/XMLParser.g4 b/tools/data-binding/xmlGrammar/XMLParser.g4
new file mode 100644
index 0000000..1b03e2d
--- /dev/null
+++ b/tools/data-binding/xmlGrammar/XMLParser.g4
@@ -0,0 +1,54 @@
+/*
+ [The "BSD licence"]
+ Copyright (c) 2013 Terence Parr
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/** XML parser derived from ANTLR v4 ref guide book example */
+parser grammar XMLParser;
+
+options { tokenVocab=XMLLexer; }
+
+document : prolog? misc* element misc*;
+
+prolog : XMLDeclOpen attribute* SPECIAL_CLOSE ;
+
+content : chardata?
+ ((element | reference | CDATA | PI | COMMENT) chardata?)* ;
+
+element : '<' elmName=Name attribute* '>' content '<' '/' Name '>'
+ | '<' elmName=Name attribute* '/>'
+ ;
+
+reference : EntityRef | CharRef ;
+
+attribute : attrName=Name '=' attrValue=STRING ; // Our STRING is AttValue in spec
+
+/** ``All text that is not markup constitutes the character data of
+ * the document.''
+ */
+chardata : TEXT | SEA_WS ;
+
+misc : COMMENT | PI | SEA_WS ;
diff --git a/tools/data-binding/xmlGrammar/build.gradle b/tools/data-binding/xmlGrammar/build.gradle
new file mode 100644
index 0000000..c9d3607
--- /dev/null
+++ b/tools/data-binding/xmlGrammar/build.gradle
@@ -0,0 +1,42 @@
+apply plugin: 'java'
+apply plugin: 'kotlin'
+apply plugin: 'application'
+apply plugin: 'maven'
+
+sourceCompatibility = config.javaTargetCompatibility
+targetCompatibility = config.javaSourceCompatibility
+
+mainClassName = "org.antlr.v4.Tool"
+
+
+repositories {
+ mavenCentral()
+}
+
+
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${config.kotlinVersion}"
+ }
+}
+
+
+run {
+ args "XMLParser.g4", "-visitor", "-o", "src/main/java/android/databinding/parser", "-package", "android.databinding.parser", "-lib", "."
+}
+
+dependencies {
+ compile "org.jetbrains.kotlin:kotlin-stdlib:${config.kotlinVersion}"
+ compile 'com.tunnelvisionlabs:antlr4:4.4'
+}
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ pom.artifactId = 'xmlGrammer'
+ }
+ }
+}
diff --git a/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLLexer.java b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLLexer.java
new file mode 100644
index 0000000..b8c79bb
--- /dev/null
+++ b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLLexer.java
@@ -0,0 +1,136 @@
+// Generated from XMLLexer.g4 by ANTLR 4.4
+package android.databinding.parser;
+
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.Lexer;
+import org.antlr.v4.runtime.atn.ATN;
+import org.antlr.v4.runtime.atn.ATNDeserializer;
+import org.antlr.v4.runtime.atn.LexerATNSimulator;
+
+public class XMLLexer extends Lexer {
+ public static final int
+ COMMENT=1, CDATA=2, DTD=3, EntityRef=4, CharRef=5, SEA_WS=6, OPEN=7, XMLDeclOpen=8,
+ TEXT=9, CLOSE=10, SPECIAL_CLOSE=11, SLASH_CLOSE=12, SLASH=13, EQUALS=14,
+ STRING=15, Name=16, S=17, PI=18;
+ public static final int INSIDE = 1;
+ public static final int PROC_INSTR = 2;
+ public static String[] modeNames = {
+ "DEFAULT_MODE", "INSIDE", "PROC_INSTR"
+ };
+
+ public static final String[] tokenNames = {
+ "'\\u0000'", "'\\u0001'", "'\\u0002'", "'\\u0003'", "'\\u0004'", "'\\u0005'",
+ "'\\u0006'", "'\\u0007'", "'\b'", "'\t'", "'\n'", "'\\u000B'", "'\f'",
+ "'\r'", "'\\u000E'", "'\\u000F'", "'\\u0010'", "'\\u0011'", "'\\u0012'"
+ };
+ public static final String[] ruleNames = {
+ "COMMENT", "CDATA", "DTD", "EntityRef", "CharRef", "SEA_WS", "OPEN", "XMLDeclOpen",
+ "SPECIAL_OPEN", "TEXT", "CLOSE", "SPECIAL_CLOSE", "SLASH_CLOSE", "SLASH",
+ "EQUALS", "STRING", "Name", "S", "HEXDIGIT", "DIGIT", "NameChar", "NameStartChar",
+ "PI", "IGNORE"
+ };
+
+
+ public XMLLexer(CharStream input) {
+ super(input);
+ _interp = new LexerATNSimulator(this,_ATN);
+ }
+
+ @Override
+ public String getGrammarFileName() { return "XMLLexer.g4"; }
+
+ @Override
+ public String[] getTokenNames() { return tokenNames; }
+
+ @Override
+ public String[] getRuleNames() { return ruleNames; }
+
+ @Override
+ public String getSerializedATN() { return _serializedATN; }
+
+ @Override
+ public String[] getModeNames() { return modeNames; }
+
+ public static final String _serializedATN =
+ "\3\uaf6f\u8320\u479d\ub75c\u4880\u1605\u191c\uab37\2\24\u00e9\b\1\b\1"+
+ "\b\1\4\2\t\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4"+
+ "\n\t\n\4\13\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t"+
+ "\21\4\22\t\22\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t"+
+ "\30\4\31\t\31\3\2\3\2\3\2\3\2\3\2\3\2\7\2<\n\2\f\2\16\2?\13\2\3\2\3\2"+
+ "\3\2\3\2\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\3\7\3P\n\3\f\3\16\3"+
+ "S\13\3\3\3\3\3\3\3\3\3\3\4\3\4\3\4\3\4\7\4]\n\4\f\4\16\4`\13\4\3\4\3\4"+
+ "\3\4\3\4\3\5\3\5\3\5\3\5\3\6\3\6\3\6\3\6\6\6n\n\6\r\6\16\6o\3\6\3\6\3"+
+ "\6\3\6\3\6\3\6\3\6\6\6y\n\6\r\6\16\6z\3\6\3\6\5\6\177\n\6\3\7\3\7\5\7"+
+ "\u0083\n\7\3\7\6\7\u0086\n\7\r\7\16\7\u0087\3\b\3\b\3\b\3\b\3\t\3\t\3"+
+ "\t\3\t\3\t\3\t\3\t\3\t\3\t\3\t\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\13\6"+
+ "\13\u00a1\n\13\r\13\16\13\u00a2\3\f\3\f\3\f\3\f\3\r\3\r\3\r\3\r\3\r\3"+
+ "\16\3\16\3\16\3\16\3\16\3\17\3\17\3\20\3\20\3\21\3\21\7\21\u00b9\n\21"+
+ "\f\21\16\21\u00bc\13\21\3\21\3\21\3\21\7\21\u00c1\n\21\f\21\16\21\u00c4"+
+ "\13\21\3\21\5\21\u00c7\n\21\3\22\3\22\7\22\u00cb\n\22\f\22\16\22\u00ce"+
+ "\13\22\3\23\3\23\3\23\3\23\3\24\3\24\3\25\3\25\3\26\3\26\3\26\3\26\5\26"+
+ "\u00dc\n\26\3\27\5\27\u00df\n\27\3\30\3\30\3\30\3\30\3\30\3\31\3\31\3"+
+ "\31\3\31\5=Q^\2\2\32\5\2\3\7\2\4\t\2\5\13\2\6\r\2\7\17\2\b\21\2\t\23\2"+
+ "\n\25\2\2\27\2\13\31\2\f\33\2\r\35\2\16\37\2\17!\2\20#\2\21%\2\22\'\2"+
+ "\23)\2\2+\2\2-\2\2/\2\2\61\2\24\63\2\2\5\2\3\4\f\4\2\13\13\"\"\4\2((>"+
+ ">\4\2$$>>\4\2))>>\5\2\13\f\17\17\"\"\5\2\62;CHch\3\2\62;\4\2/\60aa\5\2"+
+ "\u00b9\u00b9\u0302\u0371\u2041\u2042\n\2<<C\\c|\u2072\u2191\u2c02\u2ff1"+
+ "\u3003\ud801\uf902\ufdd1\ufdf2\uffff\u00f3\2\5\3\2\2\2\2\7\3\2\2\2\2\t"+
+ "\3\2\2\2\2\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2"+
+ "\2\2\25\3\2\2\2\2\27\3\2\2\2\3\31\3\2\2\2\3\33\3\2\2\2\3\35\3\2\2\2\3"+
+ "\37\3\2\2\2\3!\3\2\2\2\3#\3\2\2\2\3%\3\2\2\2\3\'\3\2\2\2\4\61\3\2\2\2"+
+ "\4\63\3\2\2\2\5\65\3\2\2\2\7D\3\2\2\2\tX\3\2\2\2\13e\3\2\2\2\r~\3\2\2"+
+ "\2\17\u0085\3\2\2\2\21\u0089\3\2\2\2\23\u008d\3\2\2\2\25\u0097\3\2\2\2"+
+ "\27\u00a0\3\2\2\2\31\u00a4\3\2\2\2\33\u00a8\3\2\2\2\35\u00ad\3\2\2\2\37"+
+ "\u00b2\3\2\2\2!\u00b4\3\2\2\2#\u00c6\3\2\2\2%\u00c8\3\2\2\2\'\u00cf\3"+
+ "\2\2\2)\u00d3\3\2\2\2+\u00d5\3\2\2\2-\u00db\3\2\2\2/\u00de\3\2\2\2\61"+
+ "\u00e0\3\2\2\2\63\u00e5\3\2\2\2\65\66\7>\2\2\66\67\7#\2\2\678\7/\2\28"+
+ "9\7/\2\29=\3\2\2\2:<\13\2\2\2;:\3\2\2\2<?\3\2\2\2=>\3\2\2\2=;\3\2\2\2"+
+ ">@\3\2\2\2?=\3\2\2\2@A\7/\2\2AB\7/\2\2BC\7@\2\2C\6\3\2\2\2DE\7>\2\2EF"+
+ "\7#\2\2FG\7]\2\2GH\7E\2\2HI\7F\2\2IJ\7C\2\2JK\7V\2\2KL\7C\2\2LM\7]\2\2"+
+ "MQ\3\2\2\2NP\13\2\2\2ON\3\2\2\2PS\3\2\2\2QR\3\2\2\2QO\3\2\2\2RT\3\2\2"+
+ "\2SQ\3\2\2\2TU\7_\2\2UV\7_\2\2VW\7@\2\2W\b\3\2\2\2XY\7>\2\2YZ\7#\2\2Z"+
+ "^\3\2\2\2[]\13\2\2\2\\[\3\2\2\2]`\3\2\2\2^_\3\2\2\2^\\\3\2\2\2_a\3\2\2"+
+ "\2`^\3\2\2\2ab\7@\2\2bc\3\2\2\2cd\b\4\2\2d\n\3\2\2\2ef\7(\2\2fg\5%\22"+
+ "\2gh\7=\2\2h\f\3\2\2\2ij\7(\2\2jk\7%\2\2km\3\2\2\2ln\5+\25\2ml\3\2\2\2"+
+ "no\3\2\2\2om\3\2\2\2op\3\2\2\2pq\3\2\2\2qr\7=\2\2r\177\3\2\2\2st\7(\2"+
+ "\2tu\7%\2\2uv\7z\2\2vx\3\2\2\2wy\5)\24\2xw\3\2\2\2yz\3\2\2\2zx\3\2\2\2"+
+ "z{\3\2\2\2{|\3\2\2\2|}\7=\2\2}\177\3\2\2\2~i\3\2\2\2~s\3\2\2\2\177\16"+
+ "\3\2\2\2\u0080\u0086\t\2\2\2\u0081\u0083\7\17\2\2\u0082\u0081\3\2\2\2"+
+ "\u0082\u0083\3\2\2\2\u0083\u0084\3\2\2\2\u0084\u0086\7\f\2\2\u0085\u0080"+
+ "\3\2\2\2\u0085\u0082\3\2\2\2\u0086\u0087\3\2\2\2\u0087\u0085\3\2\2\2\u0087"+
+ "\u0088\3\2\2\2\u0088\20\3\2\2\2\u0089\u008a\7>\2\2\u008a\u008b\3\2\2\2"+
+ "\u008b\u008c\b\b\3\2\u008c\22\3\2\2\2\u008d\u008e\7>\2\2\u008e\u008f\7"+
+ "A\2\2\u008f\u0090\7z\2\2\u0090\u0091\7o\2\2\u0091\u0092\7n\2\2\u0092\u0093"+
+ "\3\2\2\2\u0093\u0094\5\'\23\2\u0094\u0095\3\2\2\2\u0095\u0096\b\t\3\2"+
+ "\u0096\24\3\2\2\2\u0097\u0098\7>\2\2\u0098\u0099\7A\2\2\u0099\u009a\3"+
+ "\2\2\2\u009a\u009b\5%\22\2\u009b\u009c\3\2\2\2\u009c\u009d\b\n\4\2\u009d"+
+ "\u009e\b\n\5\2\u009e\26\3\2\2\2\u009f\u00a1\n\3\2\2\u00a0\u009f\3\2\2"+
+ "\2\u00a1\u00a2\3\2\2\2\u00a2\u00a0\3\2\2\2\u00a2\u00a3\3\2\2\2\u00a3\30"+
+ "\3\2\2\2\u00a4\u00a5\7@\2\2\u00a5\u00a6\3\2\2\2\u00a6\u00a7\b\f\6\2\u00a7"+
+ "\32\3\2\2\2\u00a8\u00a9\7A\2\2\u00a9\u00aa\7@\2\2\u00aa\u00ab\3\2\2\2"+
+ "\u00ab\u00ac\b\r\6\2\u00ac\34\3\2\2\2\u00ad\u00ae\7\61\2\2\u00ae\u00af"+
+ "\7@\2\2\u00af\u00b0\3\2\2\2\u00b0\u00b1\b\16\6\2\u00b1\36\3\2\2\2\u00b2"+
+ "\u00b3\7\61\2\2\u00b3 \3\2\2\2\u00b4\u00b5\7?\2\2\u00b5\"\3\2\2\2\u00b6"+
+ "\u00ba\7$\2\2\u00b7\u00b9\n\4\2\2\u00b8\u00b7\3\2\2\2\u00b9\u00bc\3\2"+
+ "\2\2\u00ba\u00b8\3\2\2\2\u00ba\u00bb\3\2\2\2\u00bb\u00bd\3\2\2\2\u00bc"+
+ "\u00ba\3\2\2\2\u00bd\u00c7\7$\2\2\u00be\u00c2\7)\2\2\u00bf\u00c1\n\5\2"+
+ "\2\u00c0\u00bf\3\2\2\2\u00c1\u00c4\3\2\2\2\u00c2\u00c0\3\2\2\2\u00c2\u00c3"+
+ "\3\2\2\2\u00c3\u00c5\3\2\2\2\u00c4\u00c2\3\2\2\2\u00c5\u00c7\7)\2\2\u00c6"+
+ "\u00b6\3\2\2\2\u00c6\u00be\3\2\2\2\u00c7$\3\2\2\2\u00c8\u00cc\5/\27\2"+
+ "\u00c9\u00cb\5-\26\2\u00ca\u00c9\3\2\2\2\u00cb\u00ce\3\2\2\2\u00cc\u00ca"+
+ "\3\2\2\2\u00cc\u00cd\3\2\2\2\u00cd&\3\2\2\2\u00ce\u00cc\3\2\2\2\u00cf"+
+ "\u00d0\t\6\2\2\u00d0\u00d1\3\2\2\2\u00d1\u00d2\b\23\2\2\u00d2(\3\2\2\2"+
+ "\u00d3\u00d4\t\7\2\2\u00d4*\3\2\2\2\u00d5\u00d6\t\b\2\2\u00d6,\3\2\2\2"+
+ "\u00d7\u00dc\5/\27\2\u00d8\u00dc\t\t\2\2\u00d9\u00dc\5+\25\2\u00da\u00dc"+
+ "\t\n\2\2\u00db\u00d7\3\2\2\2\u00db\u00d8\3\2\2\2\u00db\u00d9\3\2\2\2\u00db"+
+ "\u00da\3\2\2\2\u00dc.\3\2\2\2\u00dd\u00df\t\13\2\2\u00de\u00dd\3\2\2\2"+
+ "\u00df\60\3\2\2\2\u00e0\u00e1\7A\2\2\u00e1\u00e2\7@\2\2\u00e2\u00e3\3"+
+ "\2\2\2\u00e3\u00e4\b\30\6\2\u00e4\62\3\2\2\2\u00e5\u00e6\13\2\2\2\u00e6"+
+ "\u00e7\3\2\2\2\u00e7\u00e8\b\31\4\2\u00e8\64\3\2\2\2\25\2\3\4=Q^oz~\u0082"+
+ "\u0085\u0087\u00a2\u00ba\u00c2\u00c6\u00cc\u00db\u00de\7\b\2\2\7\3\2\5"+
+ "\2\2\7\4\2\6\2\2";
+ public static final ATN _ATN =
+ new ATNDeserializer().deserialize(_serializedATN.toCharArray());
+ static {
+ }
+}
\ No newline at end of file
diff --git a/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLLexer.tokens b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLLexer.tokens
new file mode 100644
index 0000000..cd122a4
--- /dev/null
+++ b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLLexer.tokens
@@ -0,0 +1,23 @@
+OPEN=7
+CDATA=2
+SLASH=13
+CharRef=5
+SEA_WS=6
+SPECIAL_CLOSE=11
+CLOSE=10
+DTD=3
+Name=16
+EQUALS=14
+PI=18
+S=17
+SLASH_CLOSE=12
+TEXT=9
+COMMENT=1
+XMLDeclOpen=8
+EntityRef=4
+STRING=15
+'='=14
+'/'=13
+'<'=7
+'/>'=12
+'>'=10
diff --git a/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParser.java b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParser.java
new file mode 100644
index 0000000..f18a5f0
--- /dev/null
+++ b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParser.java
@@ -0,0 +1,660 @@
+// Generated from XMLParser.g4 by ANTLR 4.4
+package android.databinding.parser;
+import org.antlr.v4.runtime.atn.*;
+import org.antlr.v4.runtime.dfa.DFA;
+import org.antlr.v4.runtime.*;
+import org.antlr.v4.runtime.misc.*;
+import org.antlr.v4.runtime.tree.*;
+import java.util.List;
+import java.util.Iterator;
+import java.util.ArrayList;
+
+public class XMLParser extends Parser {
+ public static final int
+ OPEN=7, CDATA=2, SLASH=13, CharRef=5, SEA_WS=6, SPECIAL_CLOSE=11, CLOSE=10,
+ DTD=3, Name=16, EQUALS=14, PI=18, S=17, SLASH_CLOSE=12, TEXT=9, COMMENT=1,
+ XMLDeclOpen=8, EntityRef=4, STRING=15;
+ public static final String[] tokenNames = {
+ "<INVALID>", "COMMENT", "CDATA", "DTD", "EntityRef", "CharRef", "SEA_WS",
+ "'<'", "XMLDeclOpen", "TEXT", "'>'", "SPECIAL_CLOSE", "'/>'", "'/'", "'='",
+ "STRING", "Name", "S", "PI"
+ };
+ public static final int
+ RULE_document = 0, RULE_prolog = 1, RULE_content = 2, RULE_element = 3,
+ RULE_reference = 4, RULE_attribute = 5, RULE_chardata = 6, RULE_misc = 7;
+ public static final String[] ruleNames = {
+ "document", "prolog", "content", "element", "reference", "attribute",
+ "chardata", "misc"
+ };
+
+ @Override
+ public String getGrammarFileName() { return "XMLParser.g4"; }
+
+ @Override
+ public String[] getTokenNames() { return tokenNames; }
+
+ @Override
+ public String[] getRuleNames() { return ruleNames; }
+
+ @Override
+ public String getSerializedATN() { return _serializedATN; }
+
+ public XMLParser(TokenStream input) {
+ super(input);
+ _interp = new ParserATNSimulator(this,_ATN);
+ }
+ public static class DocumentContext extends ParserRuleContext {
+ public ElementContext element() {
+ return getRuleContext(ElementContext.class,0);
+ }
+ public List<? extends MiscContext> misc() {
+ return getRuleContexts(MiscContext.class);
+ }
+ public PrologContext prolog() {
+ return getRuleContext(PrologContext.class,0);
+ }
+ public MiscContext misc(int i) {
+ return getRuleContext(MiscContext.class,i);
+ }
+ public DocumentContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_document; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).enterDocument(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).exitDocument(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof XMLParserVisitor<?> ) return ((XMLParserVisitor<? extends Result>)visitor).visitDocument(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final DocumentContext document() throws RecognitionException {
+ DocumentContext _localctx = new DocumentContext(_ctx, getState());
+ enterRule(_localctx, 0, RULE_document);
+ int _la;
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(17);
+ _la = _input.LA(1);
+ if (_la==XMLDeclOpen) {
+ {
+ setState(16); prolog();
+ }
+ }
+
+ setState(22);
+ _errHandler.sync(this);
+ _la = _input.LA(1);
+ while ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << COMMENT) | (1L << SEA_WS) | (1L << PI))) != 0)) {
+ {
+ {
+ setState(19); misc();
+ }
+ }
+ setState(24);
+ _errHandler.sync(this);
+ _la = _input.LA(1);
+ }
+ setState(25); element();
+ setState(29);
+ _errHandler.sync(this);
+ _la = _input.LA(1);
+ while ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << COMMENT) | (1L << SEA_WS) | (1L << PI))) != 0)) {
+ {
+ {
+ setState(26); misc();
+ }
+ }
+ setState(31);
+ _errHandler.sync(this);
+ _la = _input.LA(1);
+ }
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class PrologContext extends ParserRuleContext {
+ public TerminalNode SPECIAL_CLOSE() { return getToken(XMLParser.SPECIAL_CLOSE, 0); }
+ public List<? extends AttributeContext> attribute() {
+ return getRuleContexts(AttributeContext.class);
+ }
+ public AttributeContext attribute(int i) {
+ return getRuleContext(AttributeContext.class,i);
+ }
+ public TerminalNode XMLDeclOpen() { return getToken(XMLParser.XMLDeclOpen, 0); }
+ public PrologContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_prolog; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).enterProlog(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).exitProlog(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof XMLParserVisitor<?> ) return ((XMLParserVisitor<? extends Result>)visitor).visitProlog(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final PrologContext prolog() throws RecognitionException {
+ PrologContext _localctx = new PrologContext(_ctx, getState());
+ enterRule(_localctx, 2, RULE_prolog);
+ int _la;
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(32); match(XMLDeclOpen);
+ setState(36);
+ _errHandler.sync(this);
+ _la = _input.LA(1);
+ while (_la==Name) {
+ {
+ {
+ setState(33); attribute();
+ }
+ }
+ setState(38);
+ _errHandler.sync(this);
+ _la = _input.LA(1);
+ }
+ setState(39); match(SPECIAL_CLOSE);
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class ContentContext extends ParserRuleContext {
+ public List<? extends TerminalNode> PI() { return getTokens(XMLParser.PI); }
+ public List<? extends TerminalNode> CDATA() { return getTokens(XMLParser.CDATA); }
+ public List<? extends ElementContext> element() {
+ return getRuleContexts(ElementContext.class);
+ }
+ public TerminalNode PI(int i) {
+ return getToken(XMLParser.PI, i);
+ }
+ public ElementContext element(int i) {
+ return getRuleContext(ElementContext.class,i);
+ }
+ public TerminalNode COMMENT(int i) {
+ return getToken(XMLParser.COMMENT, i);
+ }
+ public TerminalNode CDATA(int i) {
+ return getToken(XMLParser.CDATA, i);
+ }
+ public ReferenceContext reference(int i) {
+ return getRuleContext(ReferenceContext.class,i);
+ }
+ public List<? extends TerminalNode> COMMENT() { return getTokens(XMLParser.COMMENT); }
+ public ChardataContext chardata(int i) {
+ return getRuleContext(ChardataContext.class,i);
+ }
+ public List<? extends ChardataContext> chardata() {
+ return getRuleContexts(ChardataContext.class);
+ }
+ public List<? extends ReferenceContext> reference() {
+ return getRuleContexts(ReferenceContext.class);
+ }
+ public ContentContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_content; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).enterContent(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).exitContent(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof XMLParserVisitor<?> ) return ((XMLParserVisitor<? extends Result>)visitor).visitContent(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final ContentContext content() throws RecognitionException {
+ ContentContext _localctx = new ContentContext(_ctx, getState());
+ enterRule(_localctx, 4, RULE_content);
+ int _la;
+ try {
+ int _alt;
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(42);
+ _la = _input.LA(1);
+ if (_la==SEA_WS || _la==TEXT) {
+ {
+ setState(41); chardata();
+ }
+ }
+
+ setState(56);
+ _errHandler.sync(this);
+ _alt = getInterpreter().adaptivePredict(_input,7,_ctx);
+ while ( _alt!=2 && _alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
+ if ( _alt==1 ) {
+ {
+ {
+ setState(49);
+ switch (_input.LA(1)) {
+ case OPEN:
+ {
+ setState(44); element();
+ }
+ break;
+ case EntityRef:
+ case CharRef:
+ {
+ setState(45); reference();
+ }
+ break;
+ case CDATA:
+ {
+ setState(46); match(CDATA);
+ }
+ break;
+ case PI:
+ {
+ setState(47); match(PI);
+ }
+ break;
+ case COMMENT:
+ {
+ setState(48); match(COMMENT);
+ }
+ break;
+ default:
+ throw new NoViableAltException(this);
+ }
+ setState(52);
+ _la = _input.LA(1);
+ if (_la==SEA_WS || _la==TEXT) {
+ {
+ setState(51); chardata();
+ }
+ }
+
+ }
+ }
+ }
+ setState(58);
+ _errHandler.sync(this);
+ _alt = getInterpreter().adaptivePredict(_input,7,_ctx);
+ }
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class ElementContext extends ParserRuleContext {
+ public Token elmName;
+ public List<? extends AttributeContext> attribute() {
+ return getRuleContexts(AttributeContext.class);
+ }
+ public AttributeContext attribute(int i) {
+ return getRuleContext(AttributeContext.class,i);
+ }
+ public TerminalNode Name(int i) {
+ return getToken(XMLParser.Name, i);
+ }
+ public List<? extends TerminalNode> Name() { return getTokens(XMLParser.Name); }
+ public ContentContext content() {
+ return getRuleContext(ContentContext.class,0);
+ }
+ public ElementContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_element; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).enterElement(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).exitElement(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof XMLParserVisitor<?> ) return ((XMLParserVisitor<? extends Result>)visitor).visitElement(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final ElementContext element() throws RecognitionException {
+ ElementContext _localctx = new ElementContext(_ctx, getState());
+ enterRule(_localctx, 6, RULE_element);
+ int _la;
+ try {
+ setState(83);
+ switch ( getInterpreter().adaptivePredict(_input,10,_ctx) ) {
+ case 1:
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(59); match(OPEN);
+ setState(60); _localctx.elmName = match(Name);
+ setState(64);
+ _errHandler.sync(this);
+ _la = _input.LA(1);
+ while (_la==Name) {
+ {
+ {
+ setState(61); attribute();
+ }
+ }
+ setState(66);
+ _errHandler.sync(this);
+ _la = _input.LA(1);
+ }
+ setState(67); match(CLOSE);
+ setState(68); content();
+ setState(69); match(OPEN);
+ setState(70); match(SLASH);
+ setState(71); match(Name);
+ setState(72); match(CLOSE);
+ }
+ break;
+
+ case 2:
+ enterOuterAlt(_localctx, 2);
+ {
+ setState(74); match(OPEN);
+ setState(75); _localctx.elmName = match(Name);
+ setState(79);
+ _errHandler.sync(this);
+ _la = _input.LA(1);
+ while (_la==Name) {
+ {
+ {
+ setState(76); attribute();
+ }
+ }
+ setState(81);
+ _errHandler.sync(this);
+ _la = _input.LA(1);
+ }
+ setState(82); match(SLASH_CLOSE);
+ }
+ break;
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class ReferenceContext extends ParserRuleContext {
+ public TerminalNode CharRef() { return getToken(XMLParser.CharRef, 0); }
+ public TerminalNode EntityRef() { return getToken(XMLParser.EntityRef, 0); }
+ public ReferenceContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_reference; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).enterReference(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).exitReference(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof XMLParserVisitor<?> ) return ((XMLParserVisitor<? extends Result>)visitor).visitReference(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final ReferenceContext reference() throws RecognitionException {
+ ReferenceContext _localctx = new ReferenceContext(_ctx, getState());
+ enterRule(_localctx, 8, RULE_reference);
+ int _la;
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(85);
+ _la = _input.LA(1);
+ if ( !(_la==EntityRef || _la==CharRef) ) {
+ _errHandler.recoverInline(this);
+ }
+ consume();
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class AttributeContext extends ParserRuleContext {
+ public Token attrName;
+ public Token attrValue;
+ public TerminalNode Name() { return getToken(XMLParser.Name, 0); }
+ public TerminalNode STRING() { return getToken(XMLParser.STRING, 0); }
+ public AttributeContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_attribute; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).enterAttribute(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).exitAttribute(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof XMLParserVisitor<?> ) return ((XMLParserVisitor<? extends Result>)visitor).visitAttribute(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final AttributeContext attribute() throws RecognitionException {
+ AttributeContext _localctx = new AttributeContext(_ctx, getState());
+ enterRule(_localctx, 10, RULE_attribute);
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(87); _localctx.attrName = match(Name);
+ setState(88); match(EQUALS);
+ setState(89); _localctx.attrValue = match(STRING);
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class ChardataContext extends ParserRuleContext {
+ public TerminalNode SEA_WS() { return getToken(XMLParser.SEA_WS, 0); }
+ public TerminalNode TEXT() { return getToken(XMLParser.TEXT, 0); }
+ public ChardataContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_chardata; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).enterChardata(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).exitChardata(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof XMLParserVisitor<?> ) return ((XMLParserVisitor<? extends Result>)visitor).visitChardata(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final ChardataContext chardata() throws RecognitionException {
+ ChardataContext _localctx = new ChardataContext(_ctx, getState());
+ enterRule(_localctx, 12, RULE_chardata);
+ int _la;
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(91);
+ _la = _input.LA(1);
+ if ( !(_la==SEA_WS || _la==TEXT) ) {
+ _errHandler.recoverInline(this);
+ }
+ consume();
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static class MiscContext extends ParserRuleContext {
+ public TerminalNode SEA_WS() { return getToken(XMLParser.SEA_WS, 0); }
+ public TerminalNode PI() { return getToken(XMLParser.PI, 0); }
+ public TerminalNode COMMENT() { return getToken(XMLParser.COMMENT, 0); }
+ public MiscContext(ParserRuleContext parent, int invokingState) {
+ super(parent, invokingState);
+ }
+ @Override public int getRuleIndex() { return RULE_misc; }
+ @Override
+ public void enterRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).enterMisc(this);
+ }
+ @Override
+ public void exitRule(ParseTreeListener listener) {
+ if ( listener instanceof XMLParserListener ) ((XMLParserListener)listener).exitMisc(this);
+ }
+ @Override
+ public <Result> Result accept(ParseTreeVisitor<? extends Result> visitor) {
+ if ( visitor instanceof XMLParserVisitor<?> ) return ((XMLParserVisitor<? extends Result>)visitor).visitMisc(this);
+ else return visitor.visitChildren(this);
+ }
+ }
+
+ @RuleVersion(0)
+ public final MiscContext misc() throws RecognitionException {
+ MiscContext _localctx = new MiscContext(_ctx, getState());
+ enterRule(_localctx, 14, RULE_misc);
+ int _la;
+ try {
+ enterOuterAlt(_localctx, 1);
+ {
+ setState(93);
+ _la = _input.LA(1);
+ if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << COMMENT) | (1L << SEA_WS) | (1L << PI))) != 0)) ) {
+ _errHandler.recoverInline(this);
+ }
+ consume();
+ }
+ }
+ catch (RecognitionException re) {
+ _localctx.exception = re;
+ _errHandler.reportError(this, re);
+ _errHandler.recover(this, re);
+ }
+ finally {
+ exitRule();
+ }
+ return _localctx;
+ }
+
+ public static final String _serializedATN =
+ "\3\uaf6f\u8320\u479d\ub75c\u4880\u1605\u191c\uab37\3\24b\4\2\t\2\4\3\t"+
+ "\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\3\2\5\2\24\n\2\3\2"+
+ "\7\2\27\n\2\f\2\16\2\32\13\2\3\2\3\2\7\2\36\n\2\f\2\16\2!\13\2\3\3\3\3"+
+ "\7\3%\n\3\f\3\16\3(\13\3\3\3\3\3\3\4\5\4-\n\4\3\4\3\4\3\4\3\4\3\4\5\4"+
+ "\64\n\4\3\4\5\4\67\n\4\7\49\n\4\f\4\16\4<\13\4\3\5\3\5\3\5\7\5A\n\5\f"+
+ "\5\16\5D\13\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\7\5P\n\5\f\5\16"+
+ "\5S\13\5\3\5\5\5V\n\5\3\6\3\6\3\7\3\7\3\7\3\7\3\b\3\b\3\t\3\t\3\t\2\2"+
+ "\2\n\2\2\4\2\6\2\b\2\n\2\f\2\16\2\20\2\2\5\3\2\6\7\4\2\b\b\13\13\5\2\3"+
+ "\3\b\b\24\24g\2\23\3\2\2\2\4\"\3\2\2\2\6,\3\2\2\2\bU\3\2\2\2\nW\3\2\2"+
+ "\2\fY\3\2\2\2\16]\3\2\2\2\20_\3\2\2\2\22\24\5\4\3\2\23\22\3\2\2\2\23\24"+
+ "\3\2\2\2\24\30\3\2\2\2\25\27\5\20\t\2\26\25\3\2\2\2\27\32\3\2\2\2\30\26"+
+ "\3\2\2\2\30\31\3\2\2\2\31\33\3\2\2\2\32\30\3\2\2\2\33\37\5\b\5\2\34\36"+
+ "\5\20\t\2\35\34\3\2\2\2\36!\3\2\2\2\37\35\3\2\2\2\37 \3\2\2\2 \3\3\2\2"+
+ "\2!\37\3\2\2\2\"&\7\n\2\2#%\5\f\7\2$#\3\2\2\2%(\3\2\2\2&$\3\2\2\2&\'\3"+
+ "\2\2\2\')\3\2\2\2(&\3\2\2\2)*\7\r\2\2*\5\3\2\2\2+-\5\16\b\2,+\3\2\2\2"+
+ ",-\3\2\2\2-:\3\2\2\2.\64\5\b\5\2/\64\5\n\6\2\60\64\7\4\2\2\61\64\7\24"+
+ "\2\2\62\64\7\3\2\2\63.\3\2\2\2\63/\3\2\2\2\63\60\3\2\2\2\63\61\3\2\2\2"+
+ "\63\62\3\2\2\2\64\66\3\2\2\2\65\67\5\16\b\2\66\65\3\2\2\2\66\67\3\2\2"+
+ "\2\679\3\2\2\28\63\3\2\2\29<\3\2\2\2:8\3\2\2\2:;\3\2\2\2;\7\3\2\2\2<:"+
+ "\3\2\2\2=>\7\t\2\2>B\7\22\2\2?A\5\f\7\2@?\3\2\2\2AD\3\2\2\2B@\3\2\2\2"+
+ "BC\3\2\2\2CE\3\2\2\2DB\3\2\2\2EF\7\f\2\2FG\5\6\4\2GH\7\t\2\2HI\7\17\2"+
+ "\2IJ\7\22\2\2JK\7\f\2\2KV\3\2\2\2LM\7\t\2\2MQ\7\22\2\2NP\5\f\7\2ON\3\2"+
+ "\2\2PS\3\2\2\2QO\3\2\2\2QR\3\2\2\2RT\3\2\2\2SQ\3\2\2\2TV\7\16\2\2U=\3"+
+ "\2\2\2UL\3\2\2\2V\t\3\2\2\2WX\t\2\2\2X\13\3\2\2\2YZ\7\22\2\2Z[\7\20\2"+
+ "\2[\\\7\21\2\2\\\r\3\2\2\2]^\t\3\2\2^\17\3\2\2\2_`\t\4\2\2`\21\3\2\2\2"+
+ "\r\23\30\37&,\63\66:BQU";
+ public static final ATN _ATN =
+ new ATNDeserializer().deserialize(_serializedATN.toCharArray());
+ static {
+ }
+}
\ No newline at end of file
diff --git a/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParser.tokens b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParser.tokens
new file mode 100644
index 0000000..b1423a1
--- /dev/null
+++ b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParser.tokens
@@ -0,0 +1,23 @@
+OPEN=7
+CDATA=2
+SLASH=13
+CharRef=5
+SEA_WS=6
+SPECIAL_CLOSE=11
+CLOSE=10
+DTD=3
+Name=16
+EQUALS=14
+PI=18
+SLASH_CLOSE=12
+S=17
+TEXT=9
+XMLDeclOpen=8
+COMMENT=1
+EntityRef=4
+STRING=15
+'='=14
+'<'=7
+'/'=13
+'/>'=12
+'>'=10
diff --git a/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParserBaseListener.java b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParserBaseListener.java
new file mode 100644
index 0000000..4c2bae2
--- /dev/null
+++ b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParserBaseListener.java
@@ -0,0 +1,144 @@
+// Generated from XMLParser.g4 by ANTLR 4.4
+package android.databinding.parser;
+
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.misc.NotNull;
+import org.antlr.v4.runtime.tree.ErrorNode;
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+/**
+ * This class provides an empty implementation of {@link XMLParserListener},
+ * which can be extended to create a listener which only needs to handle a subset
+ * of the available methods.
+ */
+public class XMLParserBaseListener implements XMLParserListener {
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterContent(@NotNull XMLParser.ContentContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitContent(@NotNull XMLParser.ContentContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterElement(@NotNull XMLParser.ElementContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitElement(@NotNull XMLParser.ElementContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterProlog(@NotNull XMLParser.PrologContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitProlog(@NotNull XMLParser.PrologContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterDocument(@NotNull XMLParser.DocumentContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitDocument(@NotNull XMLParser.DocumentContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterAttribute(@NotNull XMLParser.AttributeContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitAttribute(@NotNull XMLParser.AttributeContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterChardata(@NotNull XMLParser.ChardataContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitChardata(@NotNull XMLParser.ChardataContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterReference(@NotNull XMLParser.ReferenceContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitReference(@NotNull XMLParser.ReferenceContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterMisc(@NotNull XMLParser.MiscContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitMisc(@NotNull XMLParser.MiscContext ctx) { }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void enterEveryRule(@NotNull ParserRuleContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void exitEveryRule(@NotNull ParserRuleContext ctx) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void visitTerminal(@NotNull TerminalNode node) { }
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation does nothing.</p>
+ */
+ @Override public void visitErrorNode(@NotNull ErrorNode node) { }
+}
\ No newline at end of file
diff --git a/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParserBaseVisitor.java b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParserBaseVisitor.java
new file mode 100644
index 0000000..6b04b77
--- /dev/null
+++ b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParserBaseVisitor.java
@@ -0,0 +1,79 @@
+// Generated from XMLParser.g4 by ANTLR 4.4
+package android.databinding.parser;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.misc.NotNull;
+import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor;
+
+/**
+ * This class provides an empty implementation of {@link XMLParserVisitor},
+ * which can be extended to create a visitor which only needs to handle a subset
+ * of the available methods.
+ *
+ * @param <Result> The return type of the visit operation. Use {@link Void} for
+ * operations with no return type.
+ */
+public class XMLParserBaseVisitor<Result> extends AbstractParseTreeVisitor<Result> implements XMLParserVisitor<Result> {
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitContent(@NotNull XMLParser.ContentContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitElement(@NotNull XMLParser.ElementContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitProlog(@NotNull XMLParser.PrologContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitDocument(@NotNull XMLParser.DocumentContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitAttribute(@NotNull XMLParser.AttributeContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitChardata(@NotNull XMLParser.ChardataContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitReference(@NotNull XMLParser.ReferenceContext ctx) { return visitChildren(ctx); }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns the result of calling
+ * {@link #visitChildren} on {@code ctx}.</p>
+ */
+ @Override public Result visitMisc(@NotNull XMLParser.MiscContext ctx) { return visitChildren(ctx); }
+}
\ No newline at end of file
diff --git a/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParserListener.java b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParserListener.java
new file mode 100644
index 0000000..6bee172
--- /dev/null
+++ b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParserListener.java
@@ -0,0 +1,99 @@
+// Generated from XMLParser.g4 by ANTLR 4.4
+package android.databinding.parser;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.misc.NotNull;
+import org.antlr.v4.runtime.tree.ParseTreeListener;
+
+/**
+ * This interface defines a complete listener for a parse tree produced by
+ * {@link XMLParser}.
+ */
+public interface XMLParserListener extends ParseTreeListener {
+ /**
+ * Enter a parse tree produced by {@link XMLParser#content}.
+ * @param ctx the parse tree
+ */
+ void enterContent(@NotNull XMLParser.ContentContext ctx);
+ /**
+ * Exit a parse tree produced by {@link XMLParser#content}.
+ * @param ctx the parse tree
+ */
+ void exitContent(@NotNull XMLParser.ContentContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link XMLParser#element}.
+ * @param ctx the parse tree
+ */
+ void enterElement(@NotNull XMLParser.ElementContext ctx);
+ /**
+ * Exit a parse tree produced by {@link XMLParser#element}.
+ * @param ctx the parse tree
+ */
+ void exitElement(@NotNull XMLParser.ElementContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link XMLParser#prolog}.
+ * @param ctx the parse tree
+ */
+ void enterProlog(@NotNull XMLParser.PrologContext ctx);
+ /**
+ * Exit a parse tree produced by {@link XMLParser#prolog}.
+ * @param ctx the parse tree
+ */
+ void exitProlog(@NotNull XMLParser.PrologContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link XMLParser#document}.
+ * @param ctx the parse tree
+ */
+ void enterDocument(@NotNull XMLParser.DocumentContext ctx);
+ /**
+ * Exit a parse tree produced by {@link XMLParser#document}.
+ * @param ctx the parse tree
+ */
+ void exitDocument(@NotNull XMLParser.DocumentContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link XMLParser#attribute}.
+ * @param ctx the parse tree
+ */
+ void enterAttribute(@NotNull XMLParser.AttributeContext ctx);
+ /**
+ * Exit a parse tree produced by {@link XMLParser#attribute}.
+ * @param ctx the parse tree
+ */
+ void exitAttribute(@NotNull XMLParser.AttributeContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link XMLParser#chardata}.
+ * @param ctx the parse tree
+ */
+ void enterChardata(@NotNull XMLParser.ChardataContext ctx);
+ /**
+ * Exit a parse tree produced by {@link XMLParser#chardata}.
+ * @param ctx the parse tree
+ */
+ void exitChardata(@NotNull XMLParser.ChardataContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link XMLParser#reference}.
+ * @param ctx the parse tree
+ */
+ void enterReference(@NotNull XMLParser.ReferenceContext ctx);
+ /**
+ * Exit a parse tree produced by {@link XMLParser#reference}.
+ * @param ctx the parse tree
+ */
+ void exitReference(@NotNull XMLParser.ReferenceContext ctx);
+
+ /**
+ * Enter a parse tree produced by {@link XMLParser#misc}.
+ * @param ctx the parse tree
+ */
+ void enterMisc(@NotNull XMLParser.MiscContext ctx);
+ /**
+ * Exit a parse tree produced by {@link XMLParser#misc}.
+ * @param ctx the parse tree
+ */
+ void exitMisc(@NotNull XMLParser.MiscContext ctx);
+}
\ No newline at end of file
diff --git a/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParserVisitor.java b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParserVisitor.java
new file mode 100644
index 0000000..6a76a00
--- /dev/null
+++ b/tools/data-binding/xmlGrammar/src/main/java/android/databinding/parser/XMLParserVisitor.java
@@ -0,0 +1,70 @@
+// Generated from XMLParser.g4 by ANTLR 4.4
+package android.databinding.parser;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.misc.NotNull;
+import org.antlr.v4.runtime.tree.ParseTreeVisitor;
+
+/**
+ * This interface defines a complete generic visitor for a parse tree produced
+ * by {@link XMLParser}.
+ *
+ * @param <Result> The return type of the visit operation. Use {@link Void} for
+ * operations with no return type.
+ */
+public interface XMLParserVisitor<Result> extends ParseTreeVisitor<Result> {
+ /**
+ * Visit a parse tree produced by {@link XMLParser#content}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitContent(@NotNull XMLParser.ContentContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link XMLParser#element}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitElement(@NotNull XMLParser.ElementContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link XMLParser#prolog}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitProlog(@NotNull XMLParser.PrologContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link XMLParser#document}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitDocument(@NotNull XMLParser.DocumentContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link XMLParser#attribute}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitAttribute(@NotNull XMLParser.AttributeContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link XMLParser#chardata}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitChardata(@NotNull XMLParser.ChardataContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link XMLParser#reference}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitReference(@NotNull XMLParser.ReferenceContext ctx);
+
+ /**
+ * Visit a parse tree produced by {@link XMLParser#misc}.
+ * @param ctx the parse tree
+ * @return the visitor result
+ */
+ Result visitMisc(@NotNull XMLParser.MiscContext ctx);
+}
\ No newline at end of file
diff --git a/tools/data-binding/xmlGrammar/src/main/kotlin/xmlEditorTest.kt b/tools/data-binding/xmlGrammar/src/main/kotlin/xmlEditorTest.kt
new file mode 100644
index 0000000..48356ad
--- /dev/null
+++ b/tools/data-binding/xmlGrammar/src/main/kotlin/xmlEditorTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.parser
+
+import java.io.File
+import org.antlr.v4.runtime.ANTLRInputStream
+import org.antlr.v4.runtime.CommonTokenStream
+import java.io.FileReader
+import org.antlr.v4.runtime.Token
+import java.util.Comparator
+import kotlin.properties.Delegates
+
+fun main(vararg args : String) {
+ val f = File("/Volumes/ssd/src/data-binding/KDataBinder/samples/BindingDemo/app/src/main/res/layout/main_activity.xml")
+ antlrTest(f);
+}
+
+fun log(f : () -> String) {
+ System.out.println("LOG: ${f()}");
+}
+
+fun antlrTest(f: File) : String? {
+ val inputStream = ANTLRInputStream(FileReader(f))
+ val lexer = XMLLexer(inputStream)
+ val tokenStream = CommonTokenStream(lexer)
+ val parser = XMLParser(tokenStream)
+ val expr = parser.document()
+ log{"exp tree: ${expr.toStringTree(parser)}"}
+ val reservedElementNames = arrayListOf("variable", "import")
+ val visitor = object : XMLParserBaseVisitor<MutableList<Pair<Position, Position>>>() {
+ override fun visitAttribute(ctx: XMLParser.AttributeContext): MutableList<Pair<Position, Position>>? {
+ log{"attr:${ctx.attrName.getText()} ${ctx.attrValue.getText()}"}
+ if (ctx.attrName.getText().startsWith("bind:")) {
+
+ return arrayListOf(Pair(ctx.getStart().toPosition(), ctx.getStop().toEndPosition()))
+ } else if (ctx.attrValue.getText().startsWith("\"@{") && ctx.attrValue.getText().endsWith("}\"")) {
+ return arrayListOf(Pair(ctx.getStart().toPosition(), ctx.getStop().toEndPosition()))
+ }
+
+ //log{"visiting attr: ${ctx.getText()} at location ${ctx.getStart().toS()} ${ctx.getStop().toS()}"}
+ return super<XMLParserBaseVisitor>.visitAttribute(ctx)
+ }
+
+ override fun visitElement(ctx: XMLParser.ElementContext): MutableList<Pair<Position, Position>>? {
+ log{"elm ${ctx.elmName.getText()} || ${ctx.Name()}"}
+ if (reservedElementNames.contains(ctx.elmName?.getText()) || ctx.elmName.getText().startsWith("bind:")) {
+ return arrayListOf(Pair(ctx.getStart().toPosition(), ctx.getStop().toEndPosition()))
+ }
+ return super< XMLParserBaseVisitor>.visitElement(ctx)
+ }
+
+ override fun defaultResult(): MutableList<Pair<Position, Position>>? = arrayListOf()
+
+ override fun aggregateResult(aggregate: MutableList<Pair<Position, Position>>?, nextResult: MutableList<Pair<Position, Position>>?): MutableList<Pair<Position, Position>>? {
+ return if (aggregate == null) {
+ return nextResult
+ } else if (nextResult == null) {
+ return aggregate
+ } else {
+ aggregate.addAll(nextResult)
+ return aggregate
+ }
+ }
+ }
+ val parsedExpr = expr.accept(visitor)
+ if (parsedExpr.size() == 0) {
+ return null//nothing to strip
+ }
+ log {"result ${parsedExpr.joinToString("\n-> ")}"}
+ parsedExpr.forEach {
+ log {"${it.first.line} ${it.first.charIndex}"}
+ }
+ val out = StringBuilder()
+ val lines = f.readLines("utf-8")
+ lines.forEach { out.appendln(it) }
+
+ val sorted = parsedExpr.sortBy(object : Comparator<Pair<Position, Position>> {
+ override fun compare(o1: Pair<Position, Position>, o2: Pair<Position, Position>): Int {
+ val lineCmp = o1.first.line.compareTo(o2.first.charIndex)
+ if (lineCmp != 0) {
+ return lineCmp
+ }
+ return o1.first.line.compareTo(o2.first.charIndex)
+ }
+ })
+
+ var lineStarts = arrayListOf(0)
+
+ lines.withIndices().forEach {
+ if (it.first > 0) {
+ lineStarts.add(lineStarts[it.first - 1] + lines[it.first - 1].length() + 1)
+ }
+ }
+
+ val seperator = System.lineSeparator().charAt(0)
+
+ sorted.forEach {
+ val posStart = lineStarts[it.first.line] + it.first.charIndex
+ val posEnd = lineStarts[it.second.line] + it.second.charIndex
+ for( i in posStart..(posEnd - 1)) {
+ if (out.charAt(i) != seperator) {
+ out.setCharAt(i, ' ')
+ }
+ }
+ }
+
+ return out.toString()
+}
+
+
+fun org.antlr.v4.runtime.Token.toS() : String = "[L:${getLine()} CH:${getCharPositionInLine()}]"
+
+fun org.antlr.v4.runtime.Token.toPosition() : Position = Position(getLine() -1 , getCharPositionInLine())
+
+fun org.antlr.v4.runtime.Token.toEndPosition() : Position = Position(getLine() - 1 , getCharPositionInLine() + getText().size)
+
+data class Position(var line : Int, var charIndex : Int) {
+}