Merge "Add LockSettingsStorage tests" into lmp-mr1-dev
diff --git a/api/current.txt b/api/current.txt
index 28013d7..7df6e6e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -113,9 +113,6 @@
     field public static final java.lang.String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
     field public static final java.lang.String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
     field public static final java.lang.String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
-    field public static final java.lang.String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER";
-    field public static final java.lang.String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER";
-    field public static final java.lang.String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION";
     field public static final java.lang.String REORDER_TASKS = "android.permission.REORDER_TASKS";
     field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
     field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
@@ -28094,351 +28091,16 @@
 
 package android.telecom {
 
-  public final class AudioState implements android.os.Parcelable {
-    ctor public AudioState(boolean, int, int);
-    ctor public AudioState(android.telecom.AudioState);
-    method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.telecom.AudioState> CREATOR;
-    field public static final int ROUTE_BLUETOOTH = 2; // 0x2
-    field public static final int ROUTE_EARPIECE = 1; // 0x1
-    field public static final int ROUTE_SPEAKER = 8; // 0x8
-    field public static final int ROUTE_WIRED_HEADSET = 4; // 0x4
-    field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
-    field public final boolean isMuted;
-    field public final int route;
-    field public final int supportedRouteMask;
-  }
-
-  public abstract class Conference {
-    ctor public Conference(android.telecom.PhoneAccountHandle);
-    method public final boolean addConnection(android.telecom.Connection);
-    method public final void destroy();
-    method public final android.telecom.AudioState getAudioState();
-    method public final int getCapabilities();
-    method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
-    method public final java.util.List<android.telecom.Connection> getConnections();
-    method public final android.telecom.DisconnectCause getDisconnectCause();
-    method public final android.telecom.PhoneAccountHandle getPhoneAccountHandle();
-    method public android.telecom.Connection getPrimaryConnection();
-    method public final int getState();
-    method public void onAudioStateChanged(android.telecom.AudioState);
-    method public void onConnectionAdded(android.telecom.Connection);
-    method public void onDisconnect();
-    method public void onHold();
-    method public void onMerge(android.telecom.Connection);
-    method public void onMerge();
-    method public void onPlayDtmfTone(char);
-    method public void onSeparate(android.telecom.Connection);
-    method public void onStopDtmfTone();
-    method public void onSwap();
-    method public void onUnhold();
-    method public final void removeConnection(android.telecom.Connection);
-    method public final void setActive();
-    method public final void setCapabilities(int);
-    method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
-    method public final void setDisconnected(android.telecom.DisconnectCause);
-    method public final void setOnHold();
-  }
-
-  public abstract class Connection {
-    ctor public Connection();
-    method public static android.telecom.Connection createCanceledConnection();
-    method public static android.telecom.Connection createFailedConnection(android.telecom.DisconnectCause);
-    method public final void destroy();
-    method public final android.net.Uri getAddress();
-    method public final int getAddressPresentation();
-    method public final boolean getAudioModeIsVoip();
-    method public final android.telecom.AudioState getAudioState();
-    method public final int getCallCapabilities();
-    method public final java.lang.String getCallerDisplayName();
-    method public final int getCallerDisplayNamePresentation();
-    method public final android.telecom.Conference getConference();
-    method public final java.util.List<android.telecom.Connection> getConferenceableConnections();
-    method public final android.telecom.DisconnectCause getDisconnectCause();
-    method public final int getState();
-    method public final android.telecom.StatusHints getStatusHints();
-    method public final boolean isRingbackRequested();
-    method public void onAbort();
-    method public void onAnswer();
-    method public void onAudioStateChanged(android.telecom.AudioState);
-    method public void onDisconnect();
-    method public void onHold();
-    method public void onPlayDtmfTone(char);
-    method public void onPostDialContinue(boolean);
-    method public void onReject();
-    method public void onSeparate();
-    method public void onStateChanged(int);
-    method public void onStopDtmfTone();
-    method public void onUnhold();
-    method public final void setActive();
-    method public final void setAddress(android.net.Uri, int);
-    method public final void setAudioModeIsVoip(boolean);
-    method public final void setCallCapabilities(int);
-    method public final void setCallerDisplayName(java.lang.String, int);
-    method public final void setConferenceableConnections(java.util.List<android.telecom.Connection>);
-    method public final void setConnectionService(android.telecom.ConnectionService);
-    method public final void setDialing();
-    method public final void setDisconnected(android.telecom.DisconnectCause);
-    method public final void setInitialized();
-    method public final void setInitializing();
-    method public final void setOnHold();
-    method public final void setPostDialWait(java.lang.String);
-    method public final void setRingbackRequested(boolean);
-    method public final void setRinging();
-    method public final void setStatusHints(android.telecom.StatusHints);
-    method public static java.lang.String stateToString(int);
-    field public static final int STATE_ACTIVE = 4; // 0x4
-    field public static final int STATE_DIALING = 3; // 0x3
-    field public static final int STATE_DISCONNECTED = 6; // 0x6
-    field public static final int STATE_HOLDING = 5; // 0x5
-    field public static final int STATE_INITIALIZING = 0; // 0x0
-    field public static final int STATE_NEW = 1; // 0x1
-    field public static final int STATE_RINGING = 2; // 0x2
-  }
-
-  public final class ConnectionRequest implements android.os.Parcelable {
-    ctor public ConnectionRequest(android.telecom.PhoneAccountHandle, android.net.Uri, android.os.Bundle);
-    method public int describeContents();
-    method public android.telecom.PhoneAccountHandle getAccountHandle();
-    method public android.net.Uri getAddress();
-    method public android.os.Bundle getExtras();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.telecom.ConnectionRequest> CREATOR;
-  }
-
-  public abstract class ConnectionService extends android.app.Service {
-    ctor public ConnectionService();
-    method public final void addConference(android.telecom.Conference);
-    method public final void addExistingConnection(android.telecom.PhoneAccountHandle, android.telecom.Connection);
-    method public final void conferenceRemoteConnections(android.telecom.RemoteConnection, android.telecom.RemoteConnection);
-    method public final android.telecom.RemoteConnection createRemoteIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
-    method public final android.telecom.RemoteConnection createRemoteOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
-    method public final java.util.Collection<android.telecom.Connection> getAllConnections();
-    method public final android.os.IBinder onBind(android.content.Intent);
-    method public void onConference(android.telecom.Connection, android.telecom.Connection);
-    method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
-    method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
-    method public void onRemoteConferenceAdded(android.telecom.RemoteConference);
-    method public void onRemoteExistingConnectionAdded(android.telecom.RemoteConnection);
-    field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.ConnectionService";
-  }
-
-  public final class DisconnectCause implements android.os.Parcelable {
-    ctor public DisconnectCause(int);
-    ctor public DisconnectCause(int, java.lang.String);
-    ctor public DisconnectCause(int, java.lang.CharSequence, java.lang.CharSequence, java.lang.String);
-    ctor public DisconnectCause(int, java.lang.CharSequence, java.lang.CharSequence, java.lang.String, int);
-    method public int describeContents();
-    method public int getCode();
-    method public java.lang.CharSequence getDescription();
-    method public java.lang.CharSequence getLabel();
-    method public java.lang.String getReason();
-    method public int getTone();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final int BUSY = 7; // 0x7
-    field public static final int CANCELED = 4; // 0x4
-    field public static final int CONNECTION_MANAGER_NOT_SUPPORTED = 10; // 0xa
-    field public static final android.os.Parcelable.Creator<android.telecom.DisconnectCause> CREATOR;
-    field public static final int ERROR = 1; // 0x1
-    field public static final int LOCAL = 2; // 0x2
-    field public static final int MISSED = 5; // 0x5
-    field public static final int OTHER = 9; // 0x9
-    field public static final int REJECTED = 6; // 0x6
-    field public static final int REMOTE = 3; // 0x3
-    field public static final int RESTRICTED = 8; // 0x8
-    field public static final int UNKNOWN = 0; // 0x0
-  }
-
-  public class GatewayInfo implements android.os.Parcelable {
-    method public int describeContents();
-    method public android.net.Uri getGatewayAddress();
-    method public java.lang.String getGatewayProviderPackageName();
-    method public android.net.Uri getOriginalAddress();
-    method public boolean isEmpty();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.telecom.GatewayInfo> CREATOR;
-  }
-
-  public class PhoneAccount implements android.os.Parcelable {
-    method public static android.telecom.PhoneAccount.Builder builder(android.telecom.PhoneAccountHandle, java.lang.CharSequence);
-    method public int describeContents();
-    method public android.telecom.PhoneAccountHandle getAccountHandle();
-    method public android.net.Uri getAddress();
-    method public int getCapabilities();
-    method public int getColor();
-    method public android.graphics.drawable.Drawable getIcon(android.content.Context);
-    method public android.graphics.Bitmap getIconBitmap();
-    method public java.lang.String getIconPackageName();
-    method public int getIconResId();
-    method public java.lang.CharSequence getLabel();
-    method public java.lang.CharSequence getShortDescription();
-    method public android.net.Uri getSubscriptionAddress();
-    method public java.util.List<java.lang.String> getSupportedUriSchemes();
-    method public boolean hasCapabilities(int);
-    method public boolean supportsUriScheme(java.lang.String);
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1
-    field public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 16; // 0x10
-    field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
-    field public static final android.os.Parcelable.Creator<android.telecom.PhoneAccount> CREATOR;
-    field public static final int NO_COLOR = -1; // 0xffffffff
-    field public static final java.lang.String SCHEME_SIP = "sip";
-    field public static final java.lang.String SCHEME_TEL = "tel";
-    field public static final java.lang.String SCHEME_VOICEMAIL = "voicemail";
-  }
-
-  public static class PhoneAccount.Builder {
-    ctor public PhoneAccount.Builder(android.telecom.PhoneAccountHandle, java.lang.CharSequence);
-    ctor public PhoneAccount.Builder(android.telecom.PhoneAccount);
-    method public android.telecom.PhoneAccount build();
-    method public android.telecom.PhoneAccount.Builder setAddress(android.net.Uri);
-    method public android.telecom.PhoneAccount.Builder setCapabilities(int);
-    method public android.telecom.PhoneAccount.Builder setColor(int);
-    method public android.telecom.PhoneAccount.Builder setIconBitmap(android.graphics.Bitmap);
-    method public android.telecom.PhoneAccount.Builder setIconPackageName(java.lang.String);
-    method public android.telecom.PhoneAccount.Builder setIconResId(int);
-    method public android.telecom.PhoneAccount.Builder setShortDescription(java.lang.CharSequence);
-    method public android.telecom.PhoneAccount.Builder setSubscriptionAddress(android.net.Uri);
-    method public android.telecom.PhoneAccount.Builder setSupportedUriSchemes(java.util.List<java.lang.String>);
-  }
-
-  public class PhoneAccountHandle implements android.os.Parcelable {
-    ctor public PhoneAccountHandle(android.content.ComponentName, java.lang.String);
-    method public int describeContents();
-    method public android.content.ComponentName getComponentName();
-    method public java.lang.String getId();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.telecom.PhoneAccountHandle> CREATOR;
-  }
-
-  public final class PhoneCapabilities {
-    method public static java.lang.String toString(int);
-    field public static final int ADD_CALL = 16; // 0x10
-    field public static final int ALL = 12543; // 0x30ff
-    field public static final int DISCONNECT_FROM_CONFERENCE = 8192; // 0x2000
-    field public static final int HOLD = 1; // 0x1
-    field public static final int MANAGE_CONFERENCE = 128; // 0x80
-    field public static final int MERGE_CONFERENCE = 4; // 0x4
-    field public static final int MUTE = 64; // 0x40
-    field public static final int RESPOND_VIA_TEXT = 32; // 0x20
-    field public static final int SEPARATE_FROM_CONFERENCE = 4096; // 0x1000
-    field public static final int SUPPORT_HOLD = 2; // 0x2
-    field public static final int SWAP_CONFERENCE = 8; // 0x8
-  }
-
-  public final class RemoteConference {
-    method public void disconnect();
-    method public final int getCallCapabilities();
-    method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
-    method public final java.util.List<android.telecom.RemoteConnection> getConnections();
-    method public android.telecom.DisconnectCause getDisconnectCause();
-    method public final int getState();
-    method public void hold();
-    method public void merge();
-    method public void playDtmfTone(char);
-    method public final void registerCallback(android.telecom.RemoteConference.Callback);
-    method public void separate(android.telecom.RemoteConnection);
-    method public void setAudioState(android.telecom.AudioState);
-    method public void stopDtmfTone();
-    method public void swap();
-    method public void unhold();
-    method public final void unregisterCallback(android.telecom.RemoteConference.Callback);
-  }
-
-  public static abstract class RemoteConference.Callback {
-    ctor public RemoteConference.Callback();
-    method public void onCapabilitiesChanged(android.telecom.RemoteConference, int);
-    method public void onConferenceableConnectionsChanged(android.telecom.RemoteConference, java.util.List<android.telecom.RemoteConnection>);
-    method public void onConnectionAdded(android.telecom.RemoteConference, android.telecom.RemoteConnection);
-    method public void onConnectionRemoved(android.telecom.RemoteConference, android.telecom.RemoteConnection);
-    method public void onDestroyed(android.telecom.RemoteConference);
-    method public void onDisconnected(android.telecom.RemoteConference, android.telecom.DisconnectCause);
-    method public void onStateChanged(android.telecom.RemoteConference, int, int);
-  }
-
-  public final class RemoteConnection {
-    method public void abort();
-    method public void answer();
-    method public void disconnect();
-    method public android.net.Uri getAddress();
-    method public int getAddressPresentation();
-    method public int getCallCapabilities();
-    method public java.lang.CharSequence getCallerDisplayName();
-    method public int getCallerDisplayNamePresentation();
-    method public android.telecom.RemoteConference getConference();
-    method public java.util.List<android.telecom.RemoteConnection> getConferenceableConnections();
-    method public android.telecom.DisconnectCause getDisconnectCause();
-    method public int getState();
-    method public android.telecom.StatusHints getStatusHints();
-    method public void hold();
-    method public boolean isRingbackRequested();
-    method public boolean isVoipAudioMode();
-    method public void playDtmfTone(char);
-    method public void postDialContinue(boolean);
-    method public void registerCallback(android.telecom.RemoteConnection.Callback);
-    method public void reject();
-    method public void setAudioState(android.telecom.AudioState);
-    method public void stopDtmfTone();
-    method public void unhold();
-    method public void unregisterCallback(android.telecom.RemoteConnection.Callback);
-  }
-
-  public static abstract class RemoteConnection.Callback {
-    ctor public RemoteConnection.Callback();
-    method public void onAddressChanged(android.telecom.RemoteConnection, android.net.Uri, int);
-    method public void onCallCapabilitiesChanged(android.telecom.RemoteConnection, int);
-    method public void onCallerDisplayNameChanged(android.telecom.RemoteConnection, java.lang.String, int);
-    method public void onConferenceChanged(android.telecom.RemoteConnection, android.telecom.RemoteConference);
-    method public void onConferenceableConnectionsChanged(android.telecom.RemoteConnection, java.util.List<android.telecom.RemoteConnection>);
-    method public void onDestroyed(android.telecom.RemoteConnection);
-    method public void onDisconnected(android.telecom.RemoteConnection, android.telecom.DisconnectCause);
-    method public void onPostDialWait(android.telecom.RemoteConnection, java.lang.String);
-    method public void onRingbackRequested(android.telecom.RemoteConnection, boolean);
-    method public void onStateChanged(android.telecom.RemoteConnection, int);
-    method public void onStatusHintsChanged(android.telecom.RemoteConnection, android.telecom.StatusHints);
-    method public void onVoipAudioChanged(android.telecom.RemoteConnection, boolean);
-  }
-
-  public final class StatusHints implements android.os.Parcelable {
-    ctor public StatusHints(android.content.ComponentName, java.lang.CharSequence, int, android.os.Bundle);
-    method public int describeContents();
-    method public android.os.Bundle getExtras();
-    method public android.graphics.drawable.Drawable getIcon(android.content.Context);
-    method public int getIconResId();
-    method public java.lang.CharSequence getLabel();
-    method public android.content.ComponentName getPackageName();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.telecom.StatusHints> CREATOR;
-  }
-
   public class TelecomManager {
-    method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle);
     method public void cancelMissedCallsNotification();
-    method public void clearAccounts();
-    method public android.net.Uri getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle);
-    method public android.telecom.PhoneAccountHandle getConnectionManager();
-    method public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(java.lang.String);
-    method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle);
-    method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsForPackage();
-    method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(java.lang.String);
     method public boolean handleMmi(java.lang.String);
-    method public boolean handleMmi(android.telecom.PhoneAccountHandle, java.lang.String);
-    method public boolean hasMultipleCallCapableAccounts();
     method public boolean isInCall();
-    method public void registerPhoneAccount(android.telecom.PhoneAccount);
     method public void showInCallScreen(boolean);
-    method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle);
-    field public static final java.lang.String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
-    field public static final java.lang.String ACTION_CONNECTION_SERVICE_CONFIGURE = "android.telecom.action.CONNECTION_SERVICE_CONFIGURE";
     field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS";
     field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ','
     field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';'
-    field public static final java.lang.String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER";
     field public static final java.lang.String EXTRA_CALL_DISCONNECT_CAUSE = "android.telecom.extra.CALL_DISCONNECT_CAUSE";
     field public static final java.lang.String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecom.extra.CALL_DISCONNECT_MESSAGE";
-    field public static final java.lang.String EXTRA_CONNECTION_SERVICE = "android.telecom.extra.CONNECTION_SERVICE";
-    field public static final java.lang.String EXTRA_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.PHONE_ACCOUNT_HANDLE";
     field public static final java.lang.String EXTRA_START_CALL_WITH_SPEAKERPHONE = "android.telecom.extra.START_CALL_WITH_SPEAKERPHONE";
     field public static final java.lang.String GATEWAY_ORIGINAL_ADDRESS = "android.telecom.extra.GATEWAY_ORIGINAL_ADDRESS";
     field public static final java.lang.String GATEWAY_PROVIDER_PACKAGE = "android.telecom.extra.GATEWAY_PROVIDER_PACKAGE";
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ffe2b60..216a901 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1269,26 +1269,31 @@
         android:description="@string/permdesc_use_sip"
         android:label="@string/permlab_use_sip" />
 
-    <!-- Protects the ability to register any PhoneAccount with
+    <!-- @SystemApi Protects the ability to register any PhoneAccount with
          PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION. This capability indicates that the PhoneAccount
-         corresponds to a device SIM. -->
+         corresponds to a device SIM.
+         @hide -->
     <permission android:name="android.permission.REGISTER_SIM_SUBSCRIPTION"
         android:permissionGroup="android.permission-group.PHONE_CALLS"
         android:protectionLevel="system|signature"
         android:description="@string/permdesc_register_sim_subscription"
         android:label="@string/permlab_register_sim_subscription" />
 
-    <!-- Protects the ability to register any PhoneAccount with
-         PhoneAccount#CAPABILITY_CALL_PROVIDER. -->
+    <!-- @SystemApi Protects the ability to register any PhoneAccount with
+         PhoneAccount#CAPABILITY_CALL_PROVIDER.
+         @hide -->
     <permission android:name="android.permission.REGISTER_CALL_PROVIDER"
         android:permissionGroup="android.permission-group.PHONE_CALLS"
+        android:protectionLevel="system|signature"
         android:description="@string/permdesc_register_call_provider"
         android:label="@string/permlab_register_call_provider" />
 
-    <!-- Protects the ability to register any PhoneAccount with
-         PhoneAccount#CAPABILITY_CONNECTION_MANAGER -->
+    <!-- @SystemApi Protects the ability to register any PhoneAccount with
+         PhoneAccount#CAPABILITY_CONNECTION_MANAGER
+         @hide -->
     <permission android:name="android.permission.REGISTER_CONNECTION_MANAGER"
         android:permissionGroup="android.permission-group.PHONE_CALLS"
+        android:protectionLevel="system|signature"
         android:description="@string/permdesc_connection_manager"
         android:label="@string/permlab_connection_manager" />
 
diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml
index c4a949f..4b38ad3 100644
--- a/data/fonts/fallback_fonts.xml
+++ b/data/fonts/fallback_fonts.xml
@@ -223,7 +223,28 @@
     </family>
     <family>
         <fileset>
-            <file>NotoSansCherokee-Regular.ttf</file>
+            <file>NotoSansThaana-Regular.ttf</file>
+            <file>NotoSansThaana-Bold.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansBalinese-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansBatak-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansBuginese-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansBuhid-Regular.ttf</file>
         </fileset>
     </family>
     <family>
@@ -233,6 +254,66 @@
     </family>
     <family>
         <fileset>
+            <file>NotoSansCherokee-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansHanunoo-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansJavanese-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansLepcha-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansLimbu-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansMeeteiMayek-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansOlChiki-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansRejang-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansSaurashtra-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansSundanese-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansSylotiNagri-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
+            <file>NotoSansTagbanwa-Regular.ttf</file>
+        </fileset>
+    </family>
+    <family>
+        <fileset>
             <file>NotoSansYi-Regular.ttf</file>
         </fileset>
     </family>
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 09055c6..4493554 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -220,12 +220,61 @@
         <font weight="700" style="normal">NotoSansMyanmarUI-Bold.ttf</font>
     </family>
     <family>
-        <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+        <font weight="400" style="normal">NotoSansThaana-Regular.ttf</font>
+        <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansBalinese-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansBatak-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansBuginese-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansBuhid-Regular.ttf</font>
     </family>
     <family>
         <font weight="400" style="normal">NotoSansCanadianAboriginal-Regular.ttf</font>
     </family>
     <family>
+        <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansHanunoo-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansJavanese-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansLepcha-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansLimbu-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansMeeteiMayek-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansOlChiki-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansRejang-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansSaurashtra-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansSundanese-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansSylotiNagri-Regular.ttf</font>
+    </family>
+    <family>
+        <font weight="400" style="normal">NotoSansTagbanwa-Regular.ttf</font>
+    </family>
+    <family>
         <font weight="400" style="normal">NotoSansYi-Regular.ttf</font>
     </family>
     <family lang="zh-Hans">
diff --git a/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/land_back.png b/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/land_back.png
deleted file mode 100644
index f340a62..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/land_back.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/land_fore.png b/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/land_fore.png
deleted file mode 100644
index 2a4e595..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/land_fore.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/land_shadow.png b/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/land_shadow.png
deleted file mode 100644
index f3a3120..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/land_shadow.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/port_back.png b/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/port_back.png
deleted file mode 100644
index c40b37c..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/port_back.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/port_fore.png b/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/port_fore.png
deleted file mode 100644
index aae684b..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/port_fore.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/port_shadow.png b/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/port_shadow.png
deleted file mode 100644
index 61a0da9..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/port_shadow.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/thumb.png b/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/thumb.png
deleted file mode 100644
index e21a421..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/galaxy_nexus/thumb.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_6/land_back.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_6/land_back.png
new file mode 100644
index 0000000..06695f5
--- /dev/null
+++ b/docs/html/distribute/tools/promote/device-art-resources/nexus_6/land_back.png
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_6/land_fore.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_6/land_fore.png
new file mode 100644
index 0000000..9fe5409
--- /dev/null
+++ b/docs/html/distribute/tools/promote/device-art-resources/nexus_6/land_fore.png
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_6/land_shadow.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_6/land_shadow.png
new file mode 100644
index 0000000..99f826f
--- /dev/null
+++ b/docs/html/distribute/tools/promote/device-art-resources/nexus_6/land_shadow.png
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_6/port_back.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_6/port_back.png
new file mode 100644
index 0000000..6e1aec6
--- /dev/null
+++ b/docs/html/distribute/tools/promote/device-art-resources/nexus_6/port_back.png
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_6/port_fore.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_6/port_fore.png
new file mode 100644
index 0000000..53ec73a
--- /dev/null
+++ b/docs/html/distribute/tools/promote/device-art-resources/nexus_6/port_fore.png
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_6/port_shadow.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_6/port_shadow.png
new file mode 100644
index 0000000..66149c6
--- /dev/null
+++ b/docs/html/distribute/tools/promote/device-art-resources/nexus_6/port_shadow.png
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_6/thumb.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_6/thumb.png
new file mode 100644
index 0000000..bc1f492
--- /dev/null
+++ b/docs/html/distribute/tools/promote/device-art-resources/nexus_6/thumb.png
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_9/land_back.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_9/land_back.png
new file mode 100644
index 0000000..fdbc52c
--- /dev/null
+++ b/docs/html/distribute/tools/promote/device-art-resources/nexus_9/land_back.png
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_9/land_fore.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_9/land_fore.png
new file mode 100644
index 0000000..76fc78e
--- /dev/null
+++ b/docs/html/distribute/tools/promote/device-art-resources/nexus_9/land_fore.png
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_9/land_shadow.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_9/land_shadow.png
new file mode 100644
index 0000000..d40758f
--- /dev/null
+++ b/docs/html/distribute/tools/promote/device-art-resources/nexus_9/land_shadow.png
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_9/port_back.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_9/port_back.png
new file mode 100644
index 0000000..fd6f88f
--- /dev/null
+++ b/docs/html/distribute/tools/promote/device-art-resources/nexus_9/port_back.png
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_9/port_fore.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_9/port_fore.png
new file mode 100644
index 0000000..328ceef
--- /dev/null
+++ b/docs/html/distribute/tools/promote/device-art-resources/nexus_9/port_fore.png
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_9/port_shadow.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_9/port_shadow.png
new file mode 100644
index 0000000..13eba2f
--- /dev/null
+++ b/docs/html/distribute/tools/promote/device-art-resources/nexus_9/port_shadow.png
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_9/thumb.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_9/thumb.png
new file mode 100644
index 0000000..bb52e6c
--- /dev/null
+++ b/docs/html/distribute/tools/promote/device-art-resources/nexus_9/thumb.png
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_s/land_back.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_s/land_back.png
deleted file mode 100644
index f525e8e..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/nexus_s/land_back.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_s/land_fore.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_s/land_fore.png
deleted file mode 100644
index e26bfe1..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/nexus_s/land_fore.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_s/land_shadow.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_s/land_shadow.png
deleted file mode 100644
index ea26b73..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/nexus_s/land_shadow.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_s/port_back.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_s/port_back.png
deleted file mode 100644
index ed4ad0c..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/nexus_s/port_back.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_s/port_fore.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_s/port_fore.png
deleted file mode 100644
index 74bd077..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/nexus_s/port_fore.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_s/port_shadow.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_s/port_shadow.png
deleted file mode 100644
index bb4bec8..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/nexus_s/port_shadow.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/nexus_s/thumb.png b/docs/html/distribute/tools/promote/device-art-resources/nexus_s/thumb.png
deleted file mode 100644
index 8b9a3d9..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/nexus_s/thumb.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/xoom/land_back.png b/docs/html/distribute/tools/promote/device-art-resources/xoom/land_back.png
deleted file mode 100644
index e1eb075..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/xoom/land_back.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/xoom/land_fore.png b/docs/html/distribute/tools/promote/device-art-resources/xoom/land_fore.png
deleted file mode 100644
index 15e5f50..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/xoom/land_fore.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/xoom/land_shadow.png b/docs/html/distribute/tools/promote/device-art-resources/xoom/land_shadow.png
deleted file mode 100644
index 885508a..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/xoom/land_shadow.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/xoom/port_back.png b/docs/html/distribute/tools/promote/device-art-resources/xoom/port_back.png
deleted file mode 100644
index 290ca35..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/xoom/port_back.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/xoom/port_fore.png b/docs/html/distribute/tools/promote/device-art-resources/xoom/port_fore.png
deleted file mode 100644
index 8b3dca3..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/xoom/port_fore.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/xoom/port_shadow.png b/docs/html/distribute/tools/promote/device-art-resources/xoom/port_shadow.png
deleted file mode 100644
index 895b75e..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/xoom/port_shadow.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art-resources/xoom/thumb.png b/docs/html/distribute/tools/promote/device-art-resources/xoom/thumb.png
deleted file mode 100644
index 8fd08a4..0000000
--- a/docs/html/distribute/tools/promote/device-art-resources/xoom/thumb.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/distribute/tools/promote/device-art.jd b/docs/html/distribute/tools/promote/device-art.jd
index 1736060..3902b30 100644
--- a/docs/html/distribute/tools/promote/device-art.jd
+++ b/docs/html/distribute/tools/promote/device-art.jd
@@ -66,7 +66,7 @@
   }
 
   .device-list {
-    padding: 0;
+    padding: 1em 0 0 0;
     margin: 0;
   }
 
@@ -187,6 +187,19 @@
       portSize: [1080,1920],
     },
     {
+      id: 'nexus_6',
+      title: 'Nexus 6',
+      url: 'http://www.google.com/nexus/6/',
+      physicalSize: 6,
+      physicalHeight: 6.27,
+      density: '560DPI',
+      landRes: ['shadow', 'back', 'fore'],
+      landOffset: [489,327],
+      portRes: ['shadow', 'back', 'fore'],
+      portOffset: [327,489],
+      portSize: [1440, 2560],
+    },
+    {
       id: 'nexus_7',
       title: 'Nexus 7',
       url: 'http://www.google.com/nexus/7/',
@@ -201,6 +214,20 @@
       portSize: [800,1280]
     },
     {
+      id: 'nexus_9',
+      title: 'Nexus 9',
+      url: 'http://www.google.com/nexus/9/',
+      physicalSize: 9,
+      physicalHeight: 8.98,
+      actualResolution: [1536,2048],
+      density: 'XHDPI',
+      landRes: ['shadow', 'back', 'fore'],
+      landOffset: [514,350],
+      portRes: ['shadow', 'back', 'fore'],
+      portOffset: [348,514],
+      portSize: [1536,2048],
+    },
+    {
       id: 'nexus_10',
       title: 'Nexus 10',
       url: 'http://www.google.com/nexus/10/',
@@ -212,19 +239,6 @@
       landOffset: [227,217],
       portRes: ['shadow', 'back', 'fore'],
       portOffset: [217,223],
-      portSize: [800,1280]
-    },
-    {
-      id: 'xoom',
-      title: 'Motorola XOOM',
-      url: 'http://www.google.com/phone/detail/motorola-xoom',
-      physicalSize: 10,
-      physicalHeight: 6.61,
-      density: 'MDPI',
-      landRes: ['shadow', 'back', 'fore'],
-      landOffset: [218,191],
-      portRes: ['shadow', 'back', 'fore'],
-      portOffset: [199,200],
       portSize: [800,1280],
       archived: true
     },
@@ -256,34 +270,6 @@
       portSize: [768,1280],
       archived: true
     },
-    {
-      id: 'galaxy_nexus',
-      title: 'Galaxy Nexus',
-      url: 'http://www.android.com/devices/detail/galaxy-nexus',
-      physicalSize: 4.65,
-      physicalHeight: 5.33,
-      density: 'XHDPI',
-      landRes: ['shadow', 'back', 'fore'],
-      landOffset: [371,199],
-      portRes: ['shadow', 'back', 'fore'],
-      portOffset: [216,353],
-      portSize: [720,1280],
-      archived: true
-    },
-    {
-      id: 'nexus_s',
-      title: 'Nexus S',
-      url: 'http://www.google.com/phone/detail/nexus-s',
-      physicalSize: 4.0,
-      physicalHeight: 4.88,
-      density: 'HDPI',
-      landRes: ['shadow', 'back', 'fore'],
-      landOffset: [247,135],
-      portRes: ['shadow', 'back', 'fore'],
-      portOffset: [134,247],
-      portSize: [480,800],
-      archived: true
-    }
   ];
 
   DEVICES = DEVICES.sort(function(x, y) { return x.physicalSize - y.physicalSize; });
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 3ff9ff4..a1085d3 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -51,6 +51,7 @@
             = new ArrayMap<IBinder, IConditionListener>();
     private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
     private final CountdownConditionProvider mCountdown = new CountdownConditionProvider();
+    private final NextAlarmTracker mNextAlarmTracker;
     private final DowntimeConditionProvider mDowntime = new DowntimeConditionProvider();
     private final NextAlarmConditionProvider mNextAlarm = new NextAlarmConditionProvider();
 
@@ -63,6 +64,7 @@
         mZenModeHelper = zenModeHelper;
         mZenModeHelper.addCallback(new ZenModeHelperCallback());
         loadZenConfig();
+        mNextAlarmTracker = new NextAlarmTracker(context);
     }
 
     @Override
@@ -101,6 +103,7 @@
         mCountdown.dump(pw, filter);
         mDowntime.dump(pw, filter);
         mNextAlarm.dump(pw, filter);
+        mNextAlarmTracker.dump(pw, filter);
     }
 
     @Override
@@ -111,6 +114,7 @@
     @Override
     public void onBootPhaseAppsCanStart() {
         super.onBootPhaseAppsCanStart();
+        mNextAlarmTracker.init();
         mCountdown.attachBase(mContext);
         registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT,
                 UserHandle.USER_OWNER);
@@ -121,20 +125,13 @@
         mNextAlarm.attachBase(mContext);
         registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT,
                 UserHandle.USER_OWNER);
-        mNextAlarm.setCallback(new NextAlarmConditionProvider.Callback() {
-            @Override
-            public boolean isInDowntime() {
-                return mDowntime.isInDowntime();
-            }
-        });
+        mNextAlarm.setCallback(new NextAlarmCallback());
     }
 
     @Override
     public void onUserSwitched() {
         super.onUserSwitched();
-        if (mNextAlarm != null) {
-            mNextAlarm.onUserSwitched();
-        }
+        mNextAlarmTracker.onUserSwitched();
     }
 
     @Override
@@ -572,6 +569,23 @@
                 mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "downtimeExit");
             }
         }
+
+        @Override
+        public NextAlarmTracker getNextAlarmTracker() {
+            return mNextAlarmTracker;
+        }
+    }
+
+    private class NextAlarmCallback implements NextAlarmConditionProvider.Callback {
+        @Override
+        public boolean isInDowntime() {
+            return mDowntime.isInDowntime();
+        }
+
+        @Override
+        public NextAlarmTracker getNextAlarmTracker() {
+            return mNextAlarmTracker;
+        }
     }
 
     private static class ConditionRecord {
diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
index 881c9ad..24fd155 100644
--- a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
+++ b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
@@ -298,5 +298,6 @@
 
     public interface Callback {
         void onDowntimeChanged(int downtimeMode);
+        NextAlarmTracker getNextAlarmTracker();
     }
 }
diff --git a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java b/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
index 4a29573..35bbaa0 100644
--- a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
+++ b/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
@@ -16,34 +16,23 @@
 
 package com.android.server.notification;
 
-import android.app.ActivityManager;
 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.os.Handler;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.UserHandle;
 import android.service.notification.Condition;
 import android.service.notification.ConditionProviderService;
 import android.service.notification.IConditionProvider;
 import android.service.notification.ZenModeConfig;
-import android.util.TimeUtils;
-import android.text.format.DateFormat;
 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.util.Locale;
 
 /**
  * Built-in zen condition provider for alarm-clock-based conditions.
@@ -62,33 +51,21 @@
     private static final String TAG = "NextAlarmConditions";
     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 HOURS = 60 * MINUTES;
-    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_CONNECT = 5 * MINUTES;// for initial alarm re-registration
-    private static final long WAIT_AFTER_BOOT = 20 * SECONDS;  // for initial alarm re-registration
+
     private static final String NEXT_ALARM_PATH = "next_alarm";
     public static final ComponentName COMPONENT =
             new ComponentName("android", NextAlarmConditionProvider.class.getName());
 
     private final Context mContext = this;
-    private final H mHandler = new H();
 
-    private long mConnected;
-    private boolean mRegistered;
-    private AlarmManager mAlarmManager;
-    private int mCurrentUserId;
+    private NextAlarmTracker mTracker;
+    private boolean mConnected;
     private long mLookaheadThreshold;
-    private long mScheduledAlarmTime;
     private Callback mCallback;
     private Uri mCurrentSubscription;
-    private PowerManager.WakeLock mWakeLock;
-    private long mBootCompleted;
 
     public NextAlarmConditionProvider() {
         if (DEBUG) Slog.d(TAG, "new NextAlarmConditionProvider()");
@@ -97,14 +74,9 @@
     public void dump(PrintWriter pw, DumpFilter filter) {
         pw.println("    NextAlarmConditionProvider:");
         pw.print("      mConnected="); pw.println(mConnected);
-        pw.print("      mBootCompleted="); pw.println(mBootCompleted);
-        pw.print("      mRegistered="); pw.println(mRegistered);
-        pw.print("      mCurrentUserId="); pw.println(mCurrentUserId);
-        pw.print("      mScheduledAlarmTime="); pw.println(formatAlarmDebug(mScheduledAlarmTime));
         pw.print("      mLookaheadThreshold="); pw.print(mLookaheadThreshold);
         pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")");
         pw.print("      mCurrentSubscription="); pw.println(mCurrentSubscription);
-        pw.print("      mWakeLock="); pw.println(mWakeLock);
     }
 
     public void setCallback(Callback callback) {
@@ -114,38 +86,26 @@
     @Override
     public void onConnected() {
         if (DEBUG) Slog.d(TAG, "onConnected");
-        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-        final PowerManager p = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = p.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         mLookaheadThreshold = mContext.getResources()
                 .getInteger(R.integer.config_next_alarm_condition_lookahead_threshold_hrs) * HOURS;
-        init();
-        mConnected = System.currentTimeMillis();
-    }
-
-    public void onUserSwitched() {
-        if (DEBUG) Slog.d(TAG, "onUserSwitched");
-        if (mConnected != 0) {
-            init();
-        }
+        mConnected = true;
+        mTracker = mCallback.getNextAlarmTracker();
+        mTracker.addCallback(mTrackerCallback);
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
         if (DEBUG) Slog.d(TAG, "onDestroy");
-        if (mRegistered) {
-            mContext.unregisterReceiver(mReceiver);
-            mRegistered = false;
-        }
-        mConnected = 0;
+        mTracker.removeCallback(mTrackerCallback);
+        mConnected = false;
     }
 
     @Override
     public void onRequestConditions(int relevance) {
-        if (mConnected == 0 || (relevance & Condition.FLAG_RELEVANT_NOW) == 0) return;
+        if (!mConnected || (relevance & Condition.FLAG_RELEVANT_NOW) == 0) return;
 
-        final AlarmClockInfo nextAlarm = mAlarmManager.getNextAlarmClock(mCurrentUserId);
+        final AlarmClockInfo nextAlarm = mTracker.getNextAlarm();
         if (nextAlarm == null) return;  // no next alarm
         if (mCallback != null && mCallback.isInDowntime()) return;  // prefer downtime condition
         if (!isWithinLookaheadThreshold(nextAlarm)) return;  // alarm not within window
@@ -154,12 +114,6 @@
         notifyCondition(newConditionId(), nextAlarm, Condition.STATE_TRUE, "request");
     }
 
-    private boolean isWithinLookaheadThreshold(AlarmClockInfo alarm) {
-        if (alarm == null) return false;
-        final long delta = getEarlyTriggerTime(alarm) - System.currentTimeMillis();
-        return delta > 0 && (mLookaheadThreshold <= 0 || delta < mLookaheadThreshold);
-    }
-
     @Override
     public void onSubscribe(Uri conditionId) {
         if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
@@ -168,79 +122,7 @@
             return;
         }
         mCurrentSubscription = conditionId;
-        mHandler.postEvaluate(0);
-    }
-
-    private 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 (mConnected > 0) return (time - mConnected) > WAIT_AFTER_CONNECT;
-        return true;
-    }
-
-    private void handleEvaluate() {
-        final AlarmClockInfo nextAlarm = mAlarmManager.getNextAlarmClock(mCurrentUserId);
-        final long triggerTime = getEarlyTriggerTime(nextAlarm);
-        final boolean withinThreshold = isWithinLookaheadThreshold(nextAlarm);
-        final long now = System.currentTimeMillis();
-        final boolean booted = isDoneWaitingAfterBoot(now);
-        if (DEBUG) Slog.d(TAG, "handleEvaluate mCurrentSubscription=" + mCurrentSubscription
-                + " nextAlarm=" + formatAlarmDebug(triggerTime)
-                + " withinThreshold=" + withinThreshold
-                + " booted=" + booted);
-        if (mCurrentSubscription == null) return;  // no one cares
-        if (!booted) {
-            // we don't know yet
-            notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_UNKNOWN, "!booted");
-            final long recheckTime = (mBootCompleted > 0 ? mBootCompleted : now) + WAIT_AFTER_BOOT;
-            rescheduleAlarm(recheckTime);
-            return;
-        }
-        if (!withinThreshold) {
-            // triggertime invalid or in the past, condition = false
-            notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_FALSE, "!within");
-            mCurrentSubscription = null;
-            return;
-        }
-        // triggertime in the future, condition = true, schedule alarm
-        notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_TRUE, "within");
-        rescheduleAlarm(triggerTime);
-    }
-
-    private static String formatDuration(long millis) {
-        final StringBuilder sb = new StringBuilder();
-        TimeUtils.formatDuration(millis, sb);
-        return sb.toString();
-    }
-
-    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 void notifyCondition(Uri id, AlarmClockInfo alarm, int state, String reason) {
-        final String formattedAlarm = alarm == null ? "" : formatAlarm(alarm.getTriggerTime());
-        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));
+        mTracker.evaluate();
     }
 
     @Override
@@ -248,7 +130,6 @@
         if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
         if (conditionId != null && conditionId.equals(mCurrentSubscription)) {
             mCurrentSubscription = null;
-            rescheduleAlarm(0);
         }
     }
 
@@ -260,11 +141,27 @@
         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() {
         return new Uri.Builder().scheme(Condition.SCHEME)
                 .authority(ZenModeConfig.SYSTEM_AUTHORITY)
                 .appendPath(NEXT_ALARM_PATH)
-                .appendPath(Integer.toString(mCurrentUserId))
+                .appendPath(Integer.toString(mTracker.getCurrentUserId()))
                 .build();
     }
 
@@ -273,81 +170,41 @@
                 && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
                 && conditionId.getPathSegments().size() == 2
                 && conditionId.getPathSegments().get(0).equals(NEXT_ALARM_PATH)
-                && conditionId.getPathSegments().get(1).equals(Integer.toString(mCurrentUserId));
+                && conditionId.getPathSegments().get(1)
+                        .equals(Integer.toString(mTracker.getCurrentUserId()));
     }
 
-    private void init() {
-        if (mRegistered) {
-            mContext.unregisterReceiver(mReceiver);
+    private void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
+        final boolean withinThreshold = isWithinLookaheadThreshold(nextAlarm);
+        if (DEBUG) Slog.d(TAG, "onEvaluate mCurrentSubscription=" + mCurrentSubscription
+                + " nextAlarmWakeup=" + mTracker.formatAlarmDebug(wakeupTime)
+                + " withinThreshold=" + withinThreshold
+                + " booted=" + booted);
+        if (mCurrentSubscription == null) return;  // no one cares
+        if (!booted) {
+            // we don't know yet
+            notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_UNKNOWN, "!booted");
+            return;
         }
-        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;
-        mHandler.postEvaluate(0);
+        if (!withinThreshold) {
+            // next alarm outside threshold or in the past, condition = false
+            notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_FALSE, "!within");
+            mCurrentSubscription = null;
+            return;
+        }
+        // next alarm in the future and within threshold, condition = true
+        notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_TRUE, "within");
     }
 
-    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();
-    }
-
-    private String formatAlarmDebug(AlarmClockInfo alarm) {
-        return formatAlarmDebug(alarm != null ? alarm.getTriggerTime() : 0);
-    }
-
-    private String formatAlarmDebug(long time) {
-        if (time <= 0) return Long.toString(time);
-        return String.format("%s (%s)", time, formatAlarm(time, "Hms", "hmsa"));
-    }
-
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+    private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() {
         @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
+        public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
+            NextAlarmConditionProvider.this.onEvaluate(nextAlarm, wakeupTime, booted);
         }
     };
 
     public interface Callback {
         boolean isInDowntime();
-    }
-
-    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();
-            }
-        }
+        NextAlarmTracker getNextAlarmTracker();
     }
 }
diff --git a/services/core/java/com/android/server/notification/NextAlarmTracker.java b/services/core/java/com/android/server/notification/NextAlarmTracker.java
new file mode 100644
index 0000000..d197afd
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NextAlarmTracker.java
@@ -0,0 +1,263 @@
+/*
+ * 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;
+    }
+
+    private 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();
+    }
+
+    private 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/telecomm/java/android/telecom/AudioState.java b/telecomm/java/android/telecom/AudioState.java
index 43da38f..f78ce29 100644
--- a/telecomm/java/android/telecom/AudioState.java
+++ b/telecomm/java/android/telecom/AudioState.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -24,7 +25,9 @@
 /**
  *  Encapsulates the telecom audio state, including the current audio routing, supported audio
  *  routing and mute.
+ *  @hide
  */
+@SystemApi
 public final class AudioState implements Parcelable {
     /** Direct the audio stream through the device's earpiece. */
     public static final int ROUTE_EARPIECE      = 0x00000001;
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 32bdbe0..215c682 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -16,6 +16,8 @@
 
 package android.telecom;
 
+import android.annotation.SystemApi;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -25,7 +27,9 @@
 
 /**
  * Represents a conference call which can contain any number of {@link Connection} objects.
+ * @hide
  */
+@SystemApi
 public abstract class Conference {
 
     /** @hide */
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 61b471c..63b44a6 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -19,6 +19,7 @@
 import com.android.internal.telecom.IVideoCallback;
 import com.android.internal.telecom.IVideoProvider;
 
+import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
@@ -43,7 +44,9 @@
  * Implementations are then responsible for updating the state of the {@code Connection}, and
  * must call {@link #destroy()} to signal to the framework that the {@code Connection} is no
  * longer used and associated resources may be recovered.
+ * @hide
  */
+@SystemApi
 public abstract class Connection {
 
     public static final int STATE_INITIALIZING = 0;
diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java
index 71b481b..f691c17 100644
--- a/telecomm/java/android/telecom/ConnectionRequest.java
+++ b/telecomm/java/android/telecom/ConnectionRequest.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -24,7 +25,9 @@
 /**
  * Simple data container encapsulating a request to some entity to
  * create a new {@link Connection}.
+ * @hide
  */
+@SystemApi
 public final class ConnectionRequest implements Parcelable {
 
     // TODO: Token to limit recursive invocations
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 65d48f1..48e6ff3 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -17,6 +17,7 @@
 package android.telecom;
 
 import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
 import android.app.Service;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -71,7 +72,9 @@
  * receives call-commands such as answer, reject, hold and disconnect.
  * <p>
  * When there are no more live calls, telecom will unbind from the {@code ConnectionService}.
+ * @hide
  */
+@SystemApi
 public abstract class ConnectionService extends Service {
     /**
      * The {@link Intent} that must be declared as handled by the service.
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 73bcd0c..130d676 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.media.ToneGenerator;
@@ -29,7 +30,9 @@
  * user. It is the responsibility of the {@link ConnectionService} to provide localized versions of
  * the label and description. It also may contain a reason for the disconnect, which is intended for
  * logging and not for display to the user.
+ * @hide
  */
+@SystemApi
 public final class DisconnectCause implements Parcelable {
 
     /** Disconnected because of an unknown or unspecified reason. */
diff --git a/telecomm/java/android/telecom/GatewayInfo.java b/telecomm/java/android/telecom/GatewayInfo.java
index 7105602..5b8e4ab 100644
--- a/telecomm/java/android/telecom/GatewayInfo.java
+++ b/telecomm/java/android/telecom/GatewayInfo.java
@@ -34,7 +34,9 @@
  * <li> Call the appropriate gateway address.
  * <li> Display information about how the call is being routed to the user.
  * </ol>
+ * @hide
  */
+@SystemApi
 public class GatewayInfo implements Parcelable {
 
     private final String mGatewayProviderPackageName;
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 11da0f2..a85e84d 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -54,6 +54,7 @@
     private static final int MSG_SET_POST_DIAL_WAIT = 4;
     private static final int MSG_ON_AUDIO_STATE_CHANGED = 5;
     private static final int MSG_BRING_TO_FOREGROUND = 6;
+    private static final int MSG_ON_CAN_ADD_CALL_CHANGED = 7;
 
     /** Default Handler used to consolidate binder method calls onto a single thread. */
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -91,6 +92,9 @@
                 case MSG_BRING_TO_FOREGROUND:
                     mPhone.internalBringToForeground(msg.arg1 == 1);
                     break;
+                case MSG_ON_CAN_ADD_CALL_CHANGED:
+                    mPhone.internalSetCanAddCall(msg.arg1 == 1);
+                    break;
                 default:
                     break;
             }
@@ -136,6 +140,12 @@
         public void bringToForeground(boolean showDialpad) {
             mHandler.obtainMessage(MSG_BRING_TO_FOREGROUND, showDialpad ? 1 : 0, 0).sendToTarget();
         }
+
+        @Override
+        public void onCanAddCallChanged(boolean canAddCall) {
+            mHandler.obtainMessage(MSG_ON_CAN_ADD_CALL_CHANGED, canAddCall ? 1 : 0, 0)
+                    .sendToTarget();
+        }
     }
 
     private Phone mPhone;
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 5131790..6344181 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -74,6 +74,16 @@
          * @param call A newly removed {@code Call}.
          */
         public void onCallRemoved(Phone phone, Call call) { }
+
+        /**
+         * Called when the {@code Phone} ability to add more calls changes.  If the phone cannot
+         * support more calls then {@code canAddCall} is set to {@code false}.  If it can, then it
+         * is set to {@code true}.
+         *
+         * @param phone The {@code Phone} calling this method.
+         * @param canAddCall Indicates whether an additional call can be added.
+         */
+        public void onCanAddCallChanged(Phone phone, boolean canAddCall) { }
     }
 
     // A Map allows us to track each Call by its Telecom-specified call ID
@@ -92,6 +102,8 @@
 
     private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
 
+    private boolean mCanAddCall = true;
+
     /** {@hide} */
     Phone(InCallAdapter adapter) {
         mInCallAdapter = adapter;
@@ -149,6 +161,14 @@
         fireBringToForeground(showDialpad);
     }
 
+    /** {@hide} */
+    final void internalSetCanAddCall(boolean canAddCall) {
+        if (mCanAddCall != canAddCall) {
+            mCanAddCall = canAddCall;
+            fireCanAddCallChanged(canAddCall);
+        }
+    }
+
     /**
      * Called to destroy the phone and cleanup any lingering calls.
      * @hide
@@ -191,6 +211,15 @@
     }
 
     /**
+     * Returns if the {@code Phone} can support additional calls.
+     *
+     * @return Whether the phone supports adding more calls.
+     */
+    public final boolean canAddCall() {
+        return mCanAddCall;
+    }
+
+    /**
      * Sets the microphone mute state. When this request is honored, there will be change to
      * the {@link #getAudioState()}.
      *
@@ -266,6 +295,12 @@
         }
     }
 
+    private void fireCanAddCallChanged(boolean canAddCall) {
+        for (Listener listener : mListeners) {
+            listener.onCanAddCallChanged(this, canAddCall);
+        }
+    }
+
     private void checkCallTree(ParcelableCall parcelableCall) {
         if (parcelableCall.getParentCallId() != null &&
                 !mCallByTelecomCallId.containsKey(parcelableCall.getParentCallId())) {
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 3fc1d3d..f9ee5d2 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -45,7 +46,9 @@
  * alternative options when placing a phone call. When building a {@link PhoneAccount}, the app
  * should supply a valid {@link PhoneAccountHandle} that references the {@link ConnectionService}
  * implementation Telecom will use to interact with the app.
+ * @hide
  */
+@SystemApi
 public class PhoneAccount implements Parcelable {
 
     /**
diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java
index bc4cc8c..7bcf147 100644
--- a/telecomm/java/android/telecom/PhoneAccountHandle.java
+++ b/telecomm/java/android/telecom/PhoneAccountHandle.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -33,7 +34,9 @@
  *
  * See {@link PhoneAccount},
  * {@link TelecomManager#registerPhoneAccount TelecomManager.registerPhoneAccount}.
+ * @hide
  */
+@SystemApi
 public class PhoneAccountHandle implements Parcelable {
     private ComponentName mComponentName;
     private String mId;
diff --git a/telecomm/java/android/telecom/PhoneCapabilities.java b/telecomm/java/android/telecom/PhoneCapabilities.java
index 3d3c6bd..feb7a95 100644
--- a/telecomm/java/android/telecom/PhoneCapabilities.java
+++ b/telecomm/java/android/telecom/PhoneCapabilities.java
@@ -16,10 +16,14 @@
 
 package android.telecom;
 
+import android.annotation.SystemApi;
+
 /**
  * Defines capabilities for {@link Connection}s and {@link Conference}s such as hold, swap, and
  * merge.
+ * @hide
  */
+@SystemApi
 public final class PhoneCapabilities {
     /** Call can currently be put on hold or unheld. */
     public static final int HOLD               = 0x00000001;
@@ -46,8 +50,10 @@
      */
     public static final int SWAP_CONFERENCE    = 0x00000008;
 
-    /** Call currently supports adding another call to this one. */
-    public static final int ADD_CALL           = 0x00000010;
+    /**
+     * @hide
+     */
+    public static final int UNUSED             = 0x00000010;
 
     /** Call supports responding via text option. */
     public static final int RESPOND_VIA_TEXT   = 0x00000020;
@@ -96,7 +102,7 @@
     public static final int DISCONNECT_FROM_CONFERENCE = 0x00002000;
 
     public static final int ALL = HOLD | SUPPORT_HOLD | MERGE_CONFERENCE | SWAP_CONFERENCE
-            | ADD_CALL | RESPOND_VIA_TEXT | MUTE | MANAGE_CONFERENCE | SEPARATE_FROM_CONFERENCE
+            | RESPOND_VIA_TEXT | MUTE | MANAGE_CONFERENCE | SEPARATE_FROM_CONFERENCE
             | DISCONNECT_FROM_CONFERENCE;
 
     /**
@@ -136,9 +142,6 @@
         if (can(capabilities, SWAP_CONFERENCE)) {
             builder.append(" SWAP_CONFERENCE");
         }
-        if (can(capabilities, ADD_CALL)) {
-            builder.append(" ADD_CALL");
-        }
         if (can(capabilities, RESPOND_VIA_TEXT)) {
             builder.append(" RESPOND_VIA_TEXT");
         }
diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java
index eba7580..b548274 100644
--- a/telecomm/java/android/telecom/RemoteConference.java
+++ b/telecomm/java/android/telecom/RemoteConference.java
@@ -18,6 +18,7 @@
 
 import com.android.internal.telecom.IConnectionService;
 
+import android.annotation.SystemApi;
 import android.os.RemoteException;
 
 import java.util.ArrayList;
@@ -29,7 +30,9 @@
 
 /**
  * Represents a conference call which can contain any number of {@link Connection} objects.
+ * @hide
  */
+@SystemApi
 public final class RemoteConference {
 
     public abstract static class Callback {
diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java
index 816e2bf..d70ddf6 100644
--- a/telecomm/java/android/telecom/RemoteConnection.java
+++ b/telecomm/java/android/telecom/RemoteConnection.java
@@ -20,6 +20,7 @@
 import com.android.internal.telecom.IVideoCallback;
 import com.android.internal.telecom.IVideoProvider;
 
+import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -37,7 +38,9 @@
  *
  * @see ConnectionService#createRemoteOutgoingConnection(PhoneAccountHandle, ConnectionRequest)
  * @see ConnectionService#createRemoteIncomingConnection(PhoneAccountHandle, ConnectionRequest)
+ * @hide
  */
+@SystemApi
 public final class RemoteConnection {
 
     public static abstract class Callback {
diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java
index a32eae7..dd3a639 100644
--- a/telecomm/java/android/telecom/StatusHints.java
+++ b/telecomm/java/android/telecom/StatusHints.java
@@ -16,6 +16,7 @@
 
 package android.telecom;
 
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -29,7 +30,9 @@
 
 /**
  * Contains status label and icon displayed in the in-call UI.
+ * @hide
  */
+@SystemApi
 public final class StatusHints implements Parcelable {
 
     private final ComponentName mPackageName;
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index ecf6005..05108c7 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -33,8 +33,7 @@
 
 /**
  * Provides access to information about active calls and registration/call-management functionality.
- * Apps can use methods in this class to determine the current call state. Apps can also register new
- * {@link PhoneAccount}s and get a listing of existing {@link PhoneAccount}s.
+ * Apps can use methods in this class to determine the current call state.
  * <p>
  * Apps do not instantiate this class directly; instead, they retrieve a reference to an instance
  * through {@link Context#getSystemService Context.getSystemService(Context.TELECOM_SERVICE)}.
@@ -71,7 +70,9 @@
     /**
      * The {@link android.content.Intent} action used to configure a
      * {@link android.telecom.ConnectionService}.
+     * @hide
      */
+    @SystemApi
     public static final String ACTION_CONNECTION_SERVICE_CONFIGURE =
             "android.telecom.action.CONNECTION_SERVICE_CONFIGURE";
 
@@ -84,7 +85,9 @@
     /**
      * The {@link android.content.Intent} action used to show the settings page used to configure
      * {@link PhoneAccount} preferences.
+     * @hide
      */
+    @SystemApi
     public static final String ACTION_CHANGE_PHONE_ACCOUNTS =
             "android.telecom.action.CHANGE_PHONE_ACCOUNTS";
 
@@ -114,7 +117,9 @@
      * {@link PhoneAccountHandle} to use when making the call.
      * <p class="note">
      * Retrieve with {@link android.content.Intent#getParcelableExtra(String)}.
+     * @hide
      */
+    @SystemApi
     public static final String EXTRA_PHONE_ACCOUNT_HANDLE =
             "android.telecom.extra.PHONE_ACCOUNT_HANDLE";
 
@@ -125,6 +130,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final String EXTRA_INCOMING_CALL_EXTRAS =
             "android.telecom.extra.INCOMING_CALL_EXTRAS";
 
@@ -136,6 +142,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final String EXTRA_OUTGOING_CALL_EXTRAS =
             "android.telecom.extra.OUTGOING_CALL_EXTRAS";
 
@@ -162,7 +169,9 @@
     /**
      * Optional extra for {@link android.telephony.TelephonyManager#ACTION_PHONE_STATE_CHANGED}
      * containing the component name of the associated connection service.
+     * @hide
      */
+    @SystemApi
     public static final String EXTRA_CONNECTION_SERVICE =
             "android.telecom.extra.CONNECTION_SERVICE";
 
@@ -197,7 +206,9 @@
      * {@link ConnectionService}s which interact with {@link RemoteConnection}s should only populate
      * this if the {@link android.telephony.TelephonyManager#getLine1Number()} value, as that is the
      * user's expected caller ID.
+     * @hide
      */
+    @SystemApi
     public static final String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER";
 
     /**
@@ -338,7 +349,9 @@
      * @param uriScheme The URI scheme.
      * @return The {@link PhoneAccountHandle} corresponding to the user-chosen default for outgoing
      * phone calls for a specified URI scheme.
+     * @hide
      */
+    @SystemApi
     public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) {
         try {
             if (isServiceConnected()) {
@@ -441,7 +454,9 @@
      * {@code PhoneAccount}.
      *
      * @return The phone account handle of the current connection manager.
+     * @hide
      */
+    @SystemApi
     public PhoneAccountHandle getConnectionManager() {
         return getSimCallManager();
     }
@@ -457,7 +472,9 @@
      *
      * @param uriScheme The URI scheme.
      * @return A list of {@code PhoneAccountHandle} objects supporting the URI scheme.
+     * @hide
      */
+    @SystemApi
     public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme) {
         try {
             if (isServiceConnected()) {
@@ -496,7 +513,9 @@
      *
      * @return {@code true} if the device has more than one account registered and {@code false}
      * otherwise.
+     * @hide
      */
+    @SystemApi
     public boolean hasMultipleCallCapableAccounts() {
         return getCallCapablePhoneAccounts().size() > 1;
     }
@@ -505,7 +524,9 @@
      *  Returns a list of all {@link PhoneAccount}s registered for the calling package.
      *
      * @return A list of {@code PhoneAccountHandle} objects.
+     * @hide
      */
+    @SystemApi
     public List<PhoneAccountHandle> getPhoneAccountsForPackage() {
         try {
             if (isServiceConnected()) {
@@ -523,7 +544,9 @@
      *
      * @param account The {@link PhoneAccountHandle}.
      * @return The {@link PhoneAccount} object.
+     * @hide
      */
+    @SystemApi
     public PhoneAccount getPhoneAccount(PhoneAccountHandle account) {
         try {
             if (isServiceConnected()) {
@@ -602,7 +625,10 @@
      * {@link PhoneAccountHandle#getComponentName()} does not match the package name of the app.
      *
      * @param account The complete {@link PhoneAccount}.
+     *
+     * @hide
      */
+    @SystemApi
     public void registerPhoneAccount(PhoneAccount account) {
         try {
             if (isServiceConnected()) {
@@ -617,7 +643,9 @@
      * Remove a {@link PhoneAccount} registration from the system.
      *
      * @param accountHandle A {@link PhoneAccountHandle} for the {@link PhoneAccount} to unregister.
+     * @hide
      */
+    @SystemApi
     public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
         try {
             if (isServiceConnected()) {
@@ -630,7 +658,9 @@
 
     /**
      * Remove all Accounts that belong to the calling package from the system.
+     * @hide
      */
+    @SystemApi
     public void clearAccounts() {
         try {
             if (isServiceConnected()) {
@@ -853,7 +883,9 @@
      *            {@link #registerPhoneAccount}.
      * @param extras A bundle that will be passed through to
      *            {@link ConnectionService#onCreateIncomingConnection}.
+     * @hide
      */
+    @SystemApi
     public void addNewIncomingCall(PhoneAccountHandle phoneAccount, Bundle extras) {
         try {
             if (isServiceConnected()) {
@@ -923,7 +955,9 @@
      * @param accountHandle The handle for the account the MMI code should apply to.
      * @param dialString The digits to dial.
      * @return True if the digits were processed as an MMI code, false otherwise.
+     * @hide
      */
+    @SystemApi
     public boolean handleMmi(PhoneAccountHandle accountHandle, String dialString) {
         ITelecomService service = getTelecomService();
         if (service != null) {
@@ -941,7 +975,9 @@
      * {@code null} to return a URI which will use the default account.
      * @return The URI (with the content:// scheme) specific to the specified {@link PhoneAccount}
      * for the the content retrieve.
+     * @hide
      */
+    @SystemApi
     public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle) {
         ITelecomService service = getTelecomService();
         if (service != null && accountHandle != null) {
diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
index 35f6f65..d26f6cb 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
@@ -43,4 +43,6 @@
     void onAudioStateChanged(in AudioState audioState);
 
     void bringToForeground(boolean showDialpad);
+
+    void onCanAddCallChanged(boolean canAddCall);
 }
diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp
index 848d9a1..e88c27a 100644
--- a/tools/aapt/AaptConfig.cpp
+++ b/tools/aapt/AaptConfig.cpp
@@ -21,6 +21,7 @@
 #include "AaptAssets.h"
 #include "AaptUtil.h"
 #include "ResourceFilter.h"
+#include "SdkConstants.h"
 
 using android::String8;
 using android::Vector;
@@ -241,7 +242,7 @@
 
     uint16_t minSdk = 0;
     if (config->density == ResTable_config::DENSITY_ANY) {
-        minSdk = SDK_L;
+        minSdk = SDK_LOLLIPOP;
     } else if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY
             || config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY
             || config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
@@ -800,7 +801,7 @@
     }
 
     if (config.density == ResTable_config::DENSITY_ANY) {
-        if (config.sdkVersion != SDK_L) {
+        if (config.sdkVersion != SDK_LOLLIPOP) {
             // Someone modified the sdkVersion from the default, this is not safe to assume.
             return false;
         }
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index cb34448..0e130f4 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -14,18 +14,7 @@
 #include <utils/String8.h>
 #include <utils/Vector.h>
 
-enum {
-    SDK_CUPCAKE = 3,
-    SDK_DONUT = 4,
-    SDK_ECLAIR = 5,
-    SDK_ECLAIR_0_1 = 6,
-    SDK_MR1 = 7,
-    SDK_FROYO = 8,
-    SDK_HONEYCOMB_MR2 = 13,
-    SDK_ICE_CREAM_SANDWICH = 14,
-    SDK_ICE_CREAM_SANDWICH_MR1 = 15,
-    SDK_L = 21,
-};
+#include "SdkConstants.h"
 
 /*
  * Things we can do.
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index beff604..bdc6586 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -10,6 +10,7 @@
 #include "XMLNode.h"
 #include "ResourceFilter.h"
 #include "ResourceIdCache.h"
+#include "SdkConstants.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <utils/ByteOrder.h>
@@ -4223,7 +4224,7 @@
         }
 
         const int minSdk = atoi(bundle->getMinSdkVersion());
-        if (minSdk >= SDK_L) {
+        if (minSdk >= SDK_LOLLIPOP) {
             return true;
         }
     }
@@ -4314,7 +4315,7 @@
                     }
 
                     const ConfigDescription& config = entries.keyAt(ei);
-                    if (config.sdkVersion >= SDK_L) {
+                    if (config.sdkVersion >= SDK_LOLLIPOP) {
                         // We don't need to do anything if the resource is
                         // already qualified for version 21 or higher.
                         continue;
@@ -4336,9 +4337,9 @@
                     }
 
                     // Duplicate the entry under the same configuration
-                    // but with sdkVersion == SDK_L.
+                    // but with sdkVersion == SDK_LOLLIPOP.
                     ConfigDescription newConfig(config);
-                    newConfig.sdkVersion = SDK_L;
+                    newConfig.sdkVersion = SDK_LOLLIPOP;
                     entriesToAdd.add(key_value_pair_t<ConfigDescription, sp<Entry> >(
                             newConfig, new Entry(*e)));
 
@@ -4361,7 +4362,7 @@
                     if (bundle->getVerbose()) {
                         entriesToAdd[i].value->getPos()
                                 .printf("using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.",
-                                        SDK_L,
+                                        SDK_LOLLIPOP,
                                         String8(p->getName()).string(),
                                         String8(t->getName()).string(),
                                         String8(entriesToAdd[i].value->getName()).string(),
@@ -4388,7 +4389,7 @@
         return NO_ERROR;
     }
 
-    if (target->getResourceType() == "" || target->getGroupEntry().toParams().sdkVersion >= SDK_L) {
+    if (target->getResourceType() == "" || target->getGroupEntry().toParams().sdkVersion >= SDK_LOLLIPOP) {
         // Skip resources that have no type (AndroidManifest.xml) or are already version qualified with v21
         // or higher.
         return NO_ERROR;
@@ -4424,7 +4425,7 @@
     }
 
     ConfigDescription newConfig(target->getGroupEntry().toParams());
-    newConfig.sdkVersion = SDK_L;
+    newConfig.sdkVersion = SDK_LOLLIPOP;
 
     // Look to see if we already have an overriding v21 configuration.
     sp<ConfigList> cl = getConfigList(String16(mAssets->getPackage()),
@@ -4446,7 +4447,7 @@
         if (bundle->getVerbose()) {
             SourcePos(target->getSourceFile(), -1).printf(
                     "using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.",
-                    SDK_L,
+                    SDK_LOLLIPOP,
                     mAssets->getPackage().string(),
                     newFile->getResourceType().string(),
                     String8(resourceName).string(),
diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h
new file mode 100644
index 0000000..7fd1030
--- /dev/null
+++ b/tools/aapt/SdkConstants.h
@@ -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.
+ */
+
+#ifndef H_AAPT_SDK_CONSTANTS
+#define H_AAPT_SDK_CONSTANTS
+
+enum {
+    SDK_CUPCAKE = 3,
+    SDK_DONUT = 4,
+    SDK_ECLAIR = 5,
+    SDK_ECLAIR_0_1 = 6,
+    SDK_ECLAIR_MR1 = 7,
+    SDK_FROYO = 8,
+    SDK_GINGERBREAD = 9,
+    SDK_GINGERBREAD_MR1 = 10,
+    SDK_HONEYCOMB = 11,
+    SDK_HONEYCOMB_MR1 = 12,
+    SDK_HONEYCOMB_MR2 = 13,
+    SDK_ICE_CREAM_SANDWICH = 14,
+    SDK_ICE_CREAM_SANDWICH_MR1 = 15,
+    SDK_JELLY_BEAN = 16,
+    SDK_JELLY_BEAN_MR1 = 17,
+    SDK_JELLY_BEAN_MR2 = 18,
+    SDK_KITKAT = 19,
+    SDK_KITKAT_WATCH = 20,
+    SDK_LOLLIPOP = 21,
+};
+
+#endif // H_AAPT_SDK_CONSTANTS
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-b.png b/tools/layoutlib/bridge/resources/icons/shadow-b.png
new file mode 100644
index 0000000..68f4f4b
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-b.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-bl.png b/tools/layoutlib/bridge/resources/icons/shadow-bl.png
new file mode 100644
index 0000000..ee7dbe8
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-bl.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-br.png b/tools/layoutlib/bridge/resources/icons/shadow-br.png
new file mode 100644
index 0000000..c45ad77
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-br.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-l.png b/tools/layoutlib/bridge/resources/icons/shadow-l.png
new file mode 100644
index 0000000..77d0bd0
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-l.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-r.png b/tools/layoutlib/bridge/resources/icons/shadow-r.png
new file mode 100644
index 0000000..4af7a33
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-r.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-tl.png b/tools/layoutlib/bridge/resources/icons/shadow-tl.png
new file mode 100644
index 0000000..424fb36
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-tl.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow-tr.png b/tools/layoutlib/bridge/resources/icons/shadow-tr.png
new file mode 100644
index 0000000..1fd0c772
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow-tr.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-b.png b/tools/layoutlib/bridge/resources/icons/shadow2-b.png
new file mode 100644
index 0000000..963973e
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-b.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-bl.png b/tools/layoutlib/bridge/resources/icons/shadow2-bl.png
new file mode 100644
index 0000000..7612487
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-bl.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-br.png b/tools/layoutlib/bridge/resources/icons/shadow2-br.png
new file mode 100644
index 0000000..8e20252
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-br.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-l.png b/tools/layoutlib/bridge/resources/icons/shadow2-l.png
new file mode 100644
index 0000000..2db18a0
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-l.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-r.png b/tools/layoutlib/bridge/resources/icons/shadow2-r.png
new file mode 100644
index 0000000..8e026f1
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-r.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-tl.png b/tools/layoutlib/bridge/resources/icons/shadow2-tl.png
new file mode 100644
index 0000000..a8045ed
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-tl.png
Binary files differ
diff --git a/tools/layoutlib/bridge/resources/icons/shadow2-tr.png b/tools/layoutlib/bridge/resources/icons/shadow2-tr.png
new file mode 100644
index 0000000..590373c
--- /dev/null
+++ b/tools/layoutlib/bridge/resources/icons/shadow2-tr.png
Binary files differ
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index f4282ad..8d24d38 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -141,7 +141,6 @@
      * Creates and returns a {@link Bitmap} initialized with the given stream content.
      *
      * @param input the stream from which to read the bitmap content
-     * @param createFlags
      * @param density the density associated with the bitmap
      *
      * @see Bitmap#isPremultiplied()
@@ -166,8 +165,7 @@
      * @see Bitmap#isMutable()
      * @see Bitmap#getDensity()
      */
-    public static Bitmap createBitmap(BufferedImage image, boolean isMutable,
-            Density density) throws IOException {
+    public static Bitmap createBitmap(BufferedImage image, boolean isMutable, Density density) {
         return createBitmap(image, getPremultipliedBitmapCreateFlags(isMutable), density);
     }
 
@@ -175,7 +173,6 @@
      * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
      *
      * @param image the bitmap content
-     * @param createFlags
      * @param density the density associated with the bitmap
      *
      * @see Bitmap#isPremultiplied()
@@ -183,7 +180,7 @@
      * @see Bitmap#getDensity()
      */
     public static Bitmap createBitmap(BufferedImage image, Set<BitmapCreateFlags> createFlags,
-            Density density) throws IOException {
+            Density density) {
         // create a delegate with the given image.
         Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
 
diff --git a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
new file mode 100644
index 0000000..6c949d9
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
@@ -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.
+ */
+
+package android.view;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+/**
+ * Delegate implementing the native methods of {@link RenderNode}
+ * <p/>
+ * Through the layoutlib_create tool, some native methods of RenderNode have been replaced by calls
+ * to methods of the same name in this delegate class.
+ *
+ * @see DelegateManager
+ */
+public class RenderNode_Delegate {
+
+
+    // ---- delegate manager ----
+    private static final DelegateManager<RenderNode_Delegate> sManager =
+            new DelegateManager<RenderNode_Delegate>(RenderNode_Delegate.class);
+
+
+    private float mLift;
+    @SuppressWarnings("UnusedDeclaration")
+    private String mName;
+
+    @LayoutlibDelegate
+    /*package*/ static long nCreate(String name) {
+        RenderNode_Delegate renderNodeDelegate = new RenderNode_Delegate();
+        renderNodeDelegate.mName = name;
+        return sManager.addNewDelegate(renderNodeDelegate);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDestroyRenderNode(long renderNode) {
+        sManager.removeJavaReferenceFor(renderNode);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static boolean nSetElevation(long renderNode, float lift) {
+        RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+        if (delegate != null && delegate.mLift != lift) {
+            delegate.mLift = lift;
+            return true;
+        }
+        return false;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static float nGetElevation(long renderNode) {
+        RenderNode_Delegate delegate = sManager.getDelegate(renderNode);
+        if (delegate != null) {
+            return delegate.mLift;
+        }
+        return 0f;
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/view/ShadowPainter.java b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
new file mode 100644
index 0000000..38846bd
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/ShadowPainter.java
@@ -0,0 +1,415 @@
+/*
+ * 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.view;
+
+import com.android.annotations.NonNull;
+
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.ImageIO;
+
+public class ShadowPainter {
+
+    /**
+     * Adds a drop shadow to a semi-transparent image (of an arbitrary shape) and returns it as a
+     * new image. This method attempts to mimic the same visual characteristics as the rectangular
+     * shadow painting methods in this class, {@link #createRectangularDropShadow(java.awt.image.BufferedImage)}
+     * and {@link #createSmallRectangularDropShadow(java.awt.image.BufferedImage)}.
+     *
+     * @param source the source image
+     * @param shadowSize the size of the shadow, normally {@link #SHADOW_SIZE or {@link
+     * #SMALL_SHADOW_SIZE}}
+     *
+     * @return a new image with the shadow painted in
+     */
+    @NonNull
+    public static BufferedImage createDropShadow(BufferedImage source, int shadowSize) {
+        shadowSize /= 2; // make shadow size have the same meaning as in the other shadow paint methods in this class
+
+        return createDropShadow(source, shadowSize, 0.7f, 0);
+    }
+
+    /**
+     * Creates a drop shadow of a given image and returns a new image which shows the input image on
+     * top of its drop shadow.
+     * <p/>
+     * <b>NOTE: If the shape is rectangular and opaque, consider using {@link
+     * #drawRectangleShadow(Graphics2D, int, int, int, int)} instead.</b>
+     *
+     * @param source the source image to be shadowed
+     * @param shadowSize the size of the shadow in pixels
+     * @param shadowOpacity the opacity of the shadow, with 0=transparent and 1=opaque
+     * @param shadowRgb the RGB int to use for the shadow color
+     *
+     * @return a new image with the source image on top of its shadow
+     */
+    @SuppressWarnings({"SuspiciousNameCombination", "UnnecessaryLocalVariable"})  // Imported code
+    public static BufferedImage createDropShadow(BufferedImage source, int shadowSize,
+            float shadowOpacity, int shadowRgb) {
+
+        // This code is based on
+        //      http://www.jroller.com/gfx/entry/non_rectangular_shadow
+
+        BufferedImage image;
+        int width = source.getWidth();
+        int height = source.getHeight();
+        image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE,
+                BufferedImage.TYPE_INT_ARGB);
+
+        Graphics2D g2 = image.createGraphics();
+        g2.drawImage(image, shadowSize, shadowSize, null);
+
+        int dstWidth = image.getWidth();
+        int dstHeight = image.getHeight();
+
+        int left = (shadowSize - 1) >> 1;
+        int right = shadowSize - left;
+        int xStart = left;
+        int xStop = dstWidth - right;
+        int yStart = left;
+        int yStop = dstHeight - right;
+
+        shadowRgb &= 0x00FFFFFF;
+
+        int[] aHistory = new int[shadowSize];
+        int historyIdx;
+
+        int aSum;
+
+        int[] dataBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
+        int lastPixelOffset = right * dstWidth;
+        float sumDivider = shadowOpacity / shadowSize;
+
+        // horizontal pass
+        for (int y = 0, bufferOffset = 0; y < dstHeight; y++, bufferOffset = y * dstWidth) {
+            aSum = 0;
+            historyIdx = 0;
+            for (int x = 0; x < shadowSize; x++, bufferOffset++) {
+                int a = dataBuffer[bufferOffset] >>> 24;
+                aHistory[x] = a;
+                aSum += a;
+            }
+
+            bufferOffset -= right;
+
+            for (int x = xStart; x < xStop; x++, bufferOffset++) {
+                int a = (int) (aSum * sumDivider);
+                dataBuffer[bufferOffset] = a << 24 | shadowRgb;
+
+                // subtract the oldest pixel from the sum
+                aSum -= aHistory[historyIdx];
+
+                // get the latest pixel
+                a = dataBuffer[bufferOffset + right] >>> 24;
+                aHistory[historyIdx] = a;
+                aSum += a;
+
+                if (++historyIdx >= shadowSize) {
+                    historyIdx -= shadowSize;
+                }
+            }
+        }
+        // vertical pass
+        for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {
+            aSum = 0;
+            historyIdx = 0;
+            for (int y = 0; y < shadowSize; y++, bufferOffset += dstWidth) {
+                int a = dataBuffer[bufferOffset] >>> 24;
+                aHistory[y] = a;
+                aSum += a;
+            }
+
+            bufferOffset -= lastPixelOffset;
+
+            for (int y = yStart; y < yStop; y++, bufferOffset += dstWidth) {
+                int a = (int) (aSum * sumDivider);
+                dataBuffer[bufferOffset] = a << 24 | shadowRgb;
+
+                // subtract the oldest pixel from the sum
+                aSum -= aHistory[historyIdx];
+
+                // get the latest pixel
+                a = dataBuffer[bufferOffset + lastPixelOffset] >>> 24;
+                aHistory[historyIdx] = a;
+                aSum += a;
+
+                if (++historyIdx >= shadowSize) {
+                    historyIdx -= shadowSize;
+                }
+            }
+        }
+
+        g2.drawImage(source, null, 0, 0);
+        g2.dispose();
+
+        return image;
+    }
+
+    /**
+     * Draws a rectangular drop shadow (of size {@link #SHADOW_SIZE} by {@link #SHADOW_SIZE} around
+     * the given source and returns a new image with both combined
+     *
+     * @param source the source image
+     *
+     * @return the source image with a drop shadow on the bottom and right
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public static BufferedImage createRectangularDropShadow(BufferedImage source) {
+        int type = source.getType();
+        if (type == BufferedImage.TYPE_CUSTOM) {
+            type = BufferedImage.TYPE_INT_ARGB;
+        }
+
+        int width = source.getWidth();
+        int height = source.getHeight();
+        BufferedImage image;
+        image = new BufferedImage(width + SHADOW_SIZE, height + SHADOW_SIZE, type);
+        Graphics2D g = image.createGraphics();
+        g.drawImage(source, 0, 0, null);
+        drawRectangleShadow(image, 0, 0, width, height);
+        g.dispose();
+
+        return image;
+    }
+
+    /**
+     * Draws a small rectangular drop shadow (of size {@link #SMALL_SHADOW_SIZE} by {@link
+     * #SMALL_SHADOW_SIZE} around the given source and returns a new image with both combined
+     *
+     * @param source the source image
+     *
+     * @return the source image with a drop shadow on the bottom and right
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    public static BufferedImage createSmallRectangularDropShadow(BufferedImage source) {
+        int type = source.getType();
+        if (type == BufferedImage.TYPE_CUSTOM) {
+            type = BufferedImage.TYPE_INT_ARGB;
+        }
+
+        int width = source.getWidth();
+        int height = source.getHeight();
+
+        BufferedImage image;
+        image = new BufferedImage(width + SMALL_SHADOW_SIZE, height + SMALL_SHADOW_SIZE, type);
+
+        Graphics2D g = image.createGraphics();
+        g.drawImage(source, 0, 0, null);
+        drawSmallRectangleShadow(image, 0, 0, width, height);
+        g.dispose();
+
+        return image;
+    }
+
+    /**
+     * Draws a drop shadow for the given rectangle into the given context. It will not draw anything
+     * if the rectangle is smaller than a minimum determined by the assets used to draw the shadow
+     * graphics. The size of the shadow is {@link #SHADOW_SIZE}.
+     *
+     * @param image the image to draw the shadow into
+     * @param x the left coordinate of the left hand side of the rectangle
+     * @param y the top coordinate of the top of the rectangle
+     * @param width the width of the rectangle
+     * @param height the height of the rectangle
+     */
+    public static void drawRectangleShadow(BufferedImage image,
+            int x, int y, int width, int height) {
+        Graphics2D gc = image.createGraphics();
+        try {
+            drawRectangleShadow(gc, x, y, width, height);
+        } finally {
+            gc.dispose();
+        }
+    }
+
+    /**
+     * Draws a small drop shadow for the given rectangle into the given context. It will not draw
+     * anything if the rectangle is smaller than a minimum determined by the assets used to draw the
+     * shadow graphics. The size of the shadow is {@link #SMALL_SHADOW_SIZE}.
+     *
+     * @param image the image to draw the shadow into
+     * @param x the left coordinate of the left hand side of the rectangle
+     * @param y the top coordinate of the top of the rectangle
+     * @param width the width of the rectangle
+     * @param height the height of the rectangle
+     */
+    public static void drawSmallRectangleShadow(BufferedImage image,
+            int x, int y, int width, int height) {
+        Graphics2D gc = image.createGraphics();
+        try {
+            drawSmallRectangleShadow(gc, x, y, width, height);
+        } finally {
+            gc.dispose();
+        }
+    }
+
+    /**
+     * The width and height of the drop shadow painted by
+     * {@link #drawRectangleShadow(Graphics2D, int, int, int, int)}
+     */
+    public static final int SHADOW_SIZE = 20; // DO NOT EDIT. This corresponds to bitmap graphics
+
+    /**
+     * The width and height of the drop shadow painted by
+     * {@link #drawSmallRectangleShadow(Graphics2D, int, int, int, int)}
+     */
+    public static final int SMALL_SHADOW_SIZE = 10; // DO NOT EDIT. Corresponds to bitmap graphics
+
+    /**
+     * Draws a drop shadow for the given rectangle into the given context. It will not draw anything
+     * if the rectangle is smaller than a minimum determined by the assets used to draw the shadow
+     * graphics.
+     *
+     * @param gc the graphics context to draw into
+     * @param x the left coordinate of the left hand side of the rectangle
+     * @param y the top coordinate of the top of the rectangle
+     * @param width the width of the rectangle
+     * @param height the height of the rectangle
+     */
+    public static void drawRectangleShadow(Graphics2D gc, int x, int y, int width, int height) {
+        assert ShadowBottomLeft != null;
+        assert ShadowBottomRight.getWidth(null) == SHADOW_SIZE;
+        assert ShadowBottomRight.getHeight(null) == SHADOW_SIZE;
+
+        int blWidth = ShadowBottomLeft.getWidth(null);
+        int trHeight = ShadowTopRight.getHeight(null);
+        if (width < blWidth) {
+            return;
+        }
+        if (height < trHeight) {
+            return;
+        }
+
+        gc.drawImage(ShadowBottomLeft, x - ShadowBottomLeft.getWidth(null), y + height, null);
+        gc.drawImage(ShadowBottomRight, x + width, y + height, null);
+        gc.drawImage(ShadowTopRight, x + width, y, null);
+        gc.drawImage(ShadowTopLeft, x - ShadowTopLeft.getWidth(null), y, null);
+        gc.drawImage(ShadowBottom,
+                x, y + height, x + width, y + height + ShadowBottom.getHeight(null),
+                0, 0, ShadowBottom.getWidth(null), ShadowBottom.getHeight(null), null);
+        gc.drawImage(ShadowRight,
+                x + width, y + ShadowTopRight.getHeight(null), x + width + ShadowRight.getWidth(null), y + height,
+                0, 0, ShadowRight.getWidth(null), ShadowRight.getHeight(null), null);
+        gc.drawImage(ShadowLeft,
+                x - ShadowLeft.getWidth(null), y + ShadowTopLeft.getHeight(null), x, y + height,
+                0, 0, ShadowLeft.getWidth(null), ShadowLeft.getHeight(null), null);
+    }
+
+    /**
+     * Draws a small drop shadow for the given rectangle into the given context. It will not draw
+     * anything if the rectangle is smaller than a minimum determined by the assets used to draw the
+     * shadow graphics.
+     * <p/>
+     *
+     * @param gc the graphics context to draw into
+     * @param x the left coordinate of the left hand side of the rectangle
+     * @param y the top coordinate of the top of the rectangle
+     * @param width the width of the rectangle
+     * @param height the height of the rectangle
+     */
+    public static void drawSmallRectangleShadow(Graphics2D gc, int x, int y, int width,
+            int height) {
+        assert Shadow2BottomLeft != null;
+        assert Shadow2TopRight != null;
+        assert Shadow2BottomRight.getWidth(null) == SMALL_SHADOW_SIZE;
+        assert Shadow2BottomRight.getHeight(null) == SMALL_SHADOW_SIZE;
+
+        int blWidth = Shadow2BottomLeft.getWidth(null);
+        int trHeight = Shadow2TopRight.getHeight(null);
+        if (width < blWidth) {
+            return;
+        }
+        if (height < trHeight) {
+            return;
+        }
+
+        gc.drawImage(Shadow2BottomLeft, x - Shadow2BottomLeft.getWidth(null), y + height, null);
+        gc.drawImage(Shadow2BottomRight, x + width, y + height, null);
+        gc.drawImage(Shadow2TopRight, x + width, y, null);
+        gc.drawImage(Shadow2TopLeft, x - Shadow2TopLeft.getWidth(null), y, null);
+        gc.drawImage(Shadow2Bottom,
+                x, y + height, x + width, y + height + Shadow2Bottom.getHeight(null),
+                0, 0, Shadow2Bottom.getWidth(null), Shadow2Bottom.getHeight(null), null);
+        gc.drawImage(Shadow2Right,
+                x + width, y + Shadow2TopRight.getHeight(null), x + width + Shadow2Right.getWidth(null), y + height,
+                0, 0, Shadow2Right.getWidth(null), Shadow2Right.getHeight(null), null);
+        gc.drawImage(Shadow2Left,
+                x - Shadow2Left.getWidth(null), y + Shadow2TopLeft.getHeight(null), x, y + height,
+                0, 0, Shadow2Left.getWidth(null), Shadow2Left.getHeight(null), null);
+    }
+
+    private static Image loadIcon(String name) {
+        InputStream inputStream = ShadowPainter.class.getResourceAsStream(name);
+        if (inputStream == null) {
+            throw new RuntimeException("Unable to load image for shadow: " + name);
+        }
+        try {
+            return ImageIO.read(inputStream);
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to load image for shadow:" + name, e);
+        } finally {
+            try {
+                inputStream.close();
+            } catch (IOException e) {
+                // ignore.
+            }
+        }
+    }
+
+    // Shadow graphics. This was generated by creating a drop shadow in
+    // Gimp, using the parameters x offset=10, y offset=10, blur radius=10,
+    // (for the small drop shadows x offset=10, y offset=10, blur radius=10)
+    // color=black, and opacity=51. These values attempt to make a shadow
+    // that is legible both for dark and light themes, on top of the
+    // canvas background (rgb(150,150,150). Darker shadows would tend to
+    // blend into the foreground for a dark holo screen, and lighter shadows
+    // would be hard to spot on the canvas background. If you make adjustments,
+    // make sure to check the shadow with both dark and light themes.
+    //
+    // After making the graphics, I cut out the top right, bottom left
+    // and bottom right corners as 20x20 images, and these are reproduced by
+    // painting them in the corresponding places in the target graphics context.
+    // I then grabbed a single horizontal gradient line from the middle of the
+    // right edge,and a single vertical gradient line from the bottom. These
+    // are then painted scaled/stretched in the target to fill the gaps between
+    // the three corner images.
+    //
+    // Filenames: bl=bottom left, b=bottom, br=bottom right, r=right, tr=top right
+
+    // Normal Drop Shadow
+    private static final Image ShadowBottom = loadIcon("/icons/shadow-b.png");
+    private static final Image ShadowBottomLeft = loadIcon("/icons/shadow-bl.png");
+    private static final Image ShadowBottomRight = loadIcon("/icons/shadow-br.png");
+    private static final Image ShadowRight = loadIcon("/icons/shadow-r.png");
+    private static final Image ShadowTopRight = loadIcon("/icons/shadow-tr.png");
+    private static final Image ShadowTopLeft = loadIcon("/icons/shadow-tl.png");
+    private static final Image ShadowLeft = loadIcon("/icons/shadow-l.png");
+
+    // Small Drop Shadow
+    private static final Image Shadow2Bottom = loadIcon("/icons/shadow2-b.png");
+    private static final Image Shadow2BottomLeft = loadIcon("/icons/shadow2-bl.png");
+    private static final Image Shadow2BottomRight = loadIcon("/icons/shadow2-br.png");
+    private static final Image Shadow2Right = loadIcon("/icons/shadow2-r.png");
+    private static final Image Shadow2TopRight = loadIcon("/icons/shadow2-tr.png");
+    private static final Image Shadow2TopLeft = loadIcon("/icons/shadow2-tl.png");
+    private static final Image Shadow2Left = loadIcon("/icons/shadow2-l.png");
+}
diff --git a/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
new file mode 100644
index 0000000..a6c00f7
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/ViewGroup_Delegate.java
@@ -0,0 +1,205 @@
+/*
+ * 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.view;
+
+import com.android.annotations.NonNull;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.resources.Density;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap_Delegate;
+import android.graphics.Canvas;
+import android.graphics.Outline;
+import android.graphics.Path_Delegate;
+import android.graphics.Rect;
+import android.graphics.Region.Op;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.animation.Transformation;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+/**
+ * Delegate used to provide new implementation of a select few methods of {@link ViewGroup}
+ * <p/>
+ * Through the layoutlib_create tool, the original  methods of ViewGroup have been replaced by calls
+ * to methods of the same name in this delegate class.
+ */
+public class ViewGroup_Delegate {
+
+    /**
+     * Overrides the original drawChild call in ViewGroup to draw the shadow.
+     */
+    @LayoutlibDelegate
+    /*package*/ static boolean drawChild(ViewGroup thisVG, Canvas canvas, View child,
+            long drawingTime) {
+        boolean retVal = thisVG.drawChild_Original(canvas, child, drawingTime);
+        if (child.getZ() > thisVG.getZ()) {
+            ViewOutlineProvider outlineProvider = child.getOutlineProvider();
+            Outline outline = new Outline();
+            outlineProvider.getOutline(child, outline);
+
+            if (outline.mPath != null || (outline.mRect != null && !outline.mRect.isEmpty())) {
+                int restoreTo = transformCanvas(thisVG, canvas, child);
+                drawShadow(thisVG, canvas, child, outline);
+                canvas.restoreToCount(restoreTo);
+            }
+        }
+        return retVal;
+    }
+
+    private static void drawShadow(ViewGroup parent, Canvas canvas, View child,
+            Outline outline) {
+        BufferedImage shadow = null;
+        int x = 0;
+        if (outline.mRect != null) {
+            Shadow s = getRectShadow(parent, canvas, child, outline);
+            shadow = s.mShadow;
+            x = -s.mShadowWidth;
+        } else if (outline.mPath != null) {
+            shadow = getPathShadow(child, outline, canvas);
+        }
+        if (shadow == null) {
+            return;
+        }
+        Bitmap bitmap = Bitmap_Delegate.createBitmap(shadow, false,
+                Density.getEnum(canvas.getDensity()));
+        Rect clipBounds = canvas.getClipBounds();
+        Rect newBounds = new Rect(clipBounds);
+        newBounds.left = newBounds.left + x;
+        canvas.clipRect(newBounds, Op.REPLACE);
+        canvas.drawBitmap(bitmap, x, 0, null);
+        canvas.clipRect(clipBounds, Op.REPLACE);
+    }
+
+    private static Shadow getRectShadow(ViewGroup parent, Canvas canvas, View child,
+            Outline outline) {
+        BufferedImage shadow;
+        Rect clipBounds = canvas.getClipBounds();
+        if (clipBounds.isEmpty()) {
+            return null;
+        }
+        float height = child.getZ() - parent.getZ();
+        // Draw large shadow if difference in z index is more than 10dp
+        float largeShadowThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f,
+                getMetrics(child));
+        boolean largeShadow = height > largeShadowThreshold;
+        int shadowSize = largeShadow ? ShadowPainter.SHADOW_SIZE : ShadowPainter.SMALL_SHADOW_SIZE;
+        shadow = new BufferedImage(clipBounds.width() + shadowSize, clipBounds.height(),
+                BufferedImage.TYPE_INT_ARGB);
+        Graphics2D graphics = shadow.createGraphics();
+        Rect rect = outline.mRect;
+        if (largeShadow) {
+            ShadowPainter.drawRectangleShadow(graphics,
+                    rect.left + shadowSize, rect.top, rect.width(), rect.height());
+        } else {
+            ShadowPainter.drawSmallRectangleShadow(graphics,
+                    rect.left + shadowSize, rect.top, rect.width(), rect.height());
+        }
+        graphics.dispose();
+        return new Shadow(shadow, shadowSize);
+    }
+
+    @NonNull
+    private static DisplayMetrics getMetrics(View view) {
+        Context context = view.getContext();
+        while (context instanceof ContextThemeWrapper) {
+            context = ((ContextThemeWrapper) context).getBaseContext();
+        }
+        if (context instanceof BridgeContext) {
+            return ((BridgeContext) context).getMetrics();
+        }
+        throw new RuntimeException("View " + view.getClass().getName() + " not created with the " +
+                "right context");
+    }
+
+    private static BufferedImage getPathShadow(View child, Outline outline, Canvas canvas) {
+        Rect clipBounds = canvas.getClipBounds();
+        BufferedImage image = new BufferedImage(clipBounds.width(), clipBounds.height(),
+                BufferedImage.TYPE_INT_ARGB);
+        Graphics2D graphics = image.createGraphics();
+        graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape());
+        graphics.dispose();
+        return ShadowPainter.createDropShadow(image, ((int) child.getZ()));
+    }
+
+    // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths
+    // which were never taken. Ideally, we should hook up the shadow code in the same method so
+    // that we don't have to transform the canvas twice.
+    private static int transformCanvas(ViewGroup thisVG, Canvas canvas, View child) {
+        final int restoreTo = canvas.save();
+        final boolean childHasIdentityMatrix = child.hasIdentityMatrix();
+        int flags = thisVG.mGroupFlags;
+        Transformation transformToApply = null;
+        boolean concatMatrix = false;
+        if ((flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
+            final Transformation t = thisVG.getChildTransformation();
+            final boolean hasTransform = thisVG.getChildStaticTransformation(child, t);
+            if (hasTransform) {
+                final int transformType = t.getTransformationType();
+                transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
+                concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
+            }
+        }
+        concatMatrix |= childHasIdentityMatrix;
+
+        child.computeScroll();
+        int sx = child.mScrollX;
+        int sy = child.mScrollY;
+
+        canvas.translate(child.mLeft - sx, child.mTop - sy);
+        float alpha = child.getAlpha() * child.getTransitionAlpha();
+
+        if (transformToApply != null || alpha < 1 || !childHasIdentityMatrix) {
+            if (transformToApply != null || !childHasIdentityMatrix) {
+                int transX = -sx;
+                int transY = -sy;
+
+                if (transformToApply != null) {
+                    if (concatMatrix) {
+                        // Undo the scroll translation, apply the transformation matrix,
+                        // then redo the scroll translate to get the correct result.
+                        canvas.translate(-transX, -transY);
+                        canvas.concat(transformToApply.getMatrix());
+                        canvas.translate(transX, transY);
+                    }
+                    if (!childHasIdentityMatrix) {
+                        canvas.translate(-transX, -transY);
+                        canvas.concat(child.getMatrix());
+                        canvas.translate(transX, transY);
+                    }
+                }
+
+            }
+        }
+        return restoreTo;
+    }
+
+    private static class Shadow {
+        public BufferedImage mShadow;
+        public int mShadowWidth;
+
+        public Shadow(BufferedImage shadow, int shadowWidth) {
+            mShadow = shadow;
+            mShadowWidth = shadowWidth;
+        }
+
+    }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 3d3afa4..aeb70e9 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -479,6 +479,23 @@
         StyleResourceValue style = getStyleByDynamicId(resid);
 
         if (style == null) {
+            // In some cases, style may not be a dynamic id, so we do a full search.
+            ResourceReference ref = resolveId(resid);
+            if (ref != null) {
+                if (ref.isFramework()) {
+                    ref =
+                      getRenderResources().getFrameworkResource(ResourceType.STYLE, ref.getName());
+                } else {
+                    ref =
+                      getRenderResources().getProjectResource(ResourceType.STYLE, ref.getName());
+                }
+                if (ref instanceof StyleResourceValue) {
+                    style = ((StyleResourceValue) ref);
+                }
+            }
+        }
+
+        if (style == null) {
             throw new Resources.NotFoundException();
         }
 
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
index 57fd68e..2ff8d37 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
@@ -18,6 +18,7 @@
 
 import com.android.annotations.NonNull;
 import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.RenderResources;
 import com.android.ide.common.rendering.api.ResourceValue;
 import com.android.ide.common.rendering.api.SessionParams;
 import com.android.internal.R;
@@ -37,7 +38,6 @@
 import android.view.ViewGroup.LayoutParams;
 import android.widget.ActionMenuPresenter;
 import android.widget.FrameLayout;
-import android.widget.LinearLayout;
 import android.widget.ListAdapter;
 import android.widget.ListView;
 import android.widget.RelativeLayout;
@@ -49,15 +49,23 @@
     private static final String LAYOUT_ATTR_NAME = "windowActionBarFullscreenDecorLayout";
 
     // The Action Bar
-    @NonNull private CustomActionBarWrapper mActionBar;
+    @NonNull
+    private CustomActionBarWrapper mActionBar;
 
     // Store another reference to the context so that we don't have to cast it repeatedly.
-    @NonNull private final BridgeContext mBridgeContext;
+    @NonNull
+    private final BridgeContext mBridgeContext;
 
-    @NonNull private FrameLayout mContentRoot;
+    @NonNull
+    private FrameLayout mContentRoot;
 
     // A fake parent for measuring views.
-    @Nullable private ViewGroup mMeasureParent;
+    @Nullable
+    private ViewGroup mMeasureParent;
+
+    // A Layout that contains the inflated action bar. The menu popup is added to this layout.
+    @NonNull
+    private final RelativeLayout mEnclosingLayout;
 
     /**
      * Inflate the action bar and attach it to {@code parentView}
@@ -90,20 +98,25 @@
         if (layoutId == 0) {
             throw new RuntimeException(error);
         }
+        // Create a RelativeLayout to hold the action bar. The layout is needed so that we may
+        // add the menu popup to it.
+        mEnclosingLayout = new RelativeLayout(mBridgeContext);
+        setMatchParent(mEnclosingLayout);
+        parentView.addView(mEnclosingLayout);
+
         // Inflate action bar layout.
-        View decorContent = LayoutInflater.from(context).inflate(layoutId, parentView, true);
+        View decorContent = LayoutInflater.from(context).inflate(layoutId, mEnclosingLayout, true);
 
         mActionBar = CustomActionBarWrapper.getActionBarWrapper(context, params, decorContent);
 
-        FrameLayout contentRoot = (FrameLayout) parentView.findViewById(android.R.id.content);
+        FrameLayout contentRoot = (FrameLayout) mEnclosingLayout.findViewById(android.R.id.content);
 
         // If something went wrong and we were not able to initialize the content root,
         // just add a frame layout inside this and return.
         if (contentRoot == null) {
             contentRoot = new FrameLayout(context);
-            contentRoot.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
-                    LayoutParams.MATCH_PARENT));
-            parentView.addView(contentRoot);
+            setMatchParent(contentRoot);
+            mEnclosingLayout.addView(contentRoot);
             mContentRoot = contentRoot;
         } else {
             mContentRoot = contentRoot;
@@ -112,70 +125,49 @@
         }
     }
 
+    private void setMatchParent(View view) {
+        view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
+                LayoutParams.MATCH_PARENT));
+    }
+
     /**
      * Creates a Popup and adds it to the content frame. It also adds another {@link FrameLayout} to
      * the content frame which shall serve as the new content root.
      */
     public void createMenuPopup() {
-        assert mContentRoot.getId() == android.R.id.content
+        assert mEnclosingLayout.getChildCount() == 1
                 : "Action Bar Menus have already been created.";
 
         if (!isOverflowPopupNeeded()) {
             return;
         }
 
-        // Create a layout to hold the menus and the user's content.
-        RelativeLayout layout = new RelativeLayout(mActionBar.getPopupContext());
-        layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
-                LayoutParams.MATCH_PARENT));
-        mContentRoot.addView(layout);
-        // Create a layout for the user's content.
-        FrameLayout contentRoot = new FrameLayout(mBridgeContext);
-        contentRoot.setLayoutParams(new LayoutParams(
-                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-        // Add contentRoot and menus to the layout.
-        layout.addView(contentRoot);
-        layout.addView(createMenuView());
-        // ContentRoot is now the view we just created.
-        mContentRoot = contentRoot;
-    }
-
-    /**
-     * Returns a {@link LinearLayout} containing the menu list view to be embedded in a
-     * {@link RelativeLayout}
-     */
-    @NonNull
-    private View createMenuView() {
         DisplayMetrics metrics = mBridgeContext.getMetrics();
         MenuBuilder menu = mActionBar.getMenuBuilder();
         OverflowMenuAdapter adapter = new OverflowMenuAdapter(menu, mActionBar.getPopupContext());
 
-        LinearLayout layout = new LinearLayout(mActionBar.getPopupContext());
+        ListView listView = new ListView(mActionBar.getPopupContext(), null,
+                R.attr.dropDownListViewStyle);
         RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
                 measureContentWidth(adapter), LayoutParams.WRAP_CONTENT);
         layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END);
         if (mActionBar.isSplit()) {
             layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
-            // TODO: Find correct value instead of hardcoded 10dp.
-            layoutParams.bottomMargin = getPixelValue("-10dp", metrics);
+            layoutParams.bottomMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin();
         } else {
-            layoutParams.topMargin = getPixelValue("-10dp", metrics);
+            layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
+            layoutParams.topMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin();
         }
-        layout.setLayoutParams(layoutParams);
+        layoutParams.setMarginEnd(getPixelValue("5dp", metrics));
+        listView.setLayoutParams(layoutParams);
+        listView.setAdapter(adapter);
         final TypedArray a = mActionBar.getPopupContext().obtainStyledAttributes(null,
                 R.styleable.PopupWindow, R.attr.popupMenuStyle, 0);
-        layout.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground));
-        layout.setDividerDrawable(a.getDrawable(R.attr.actionBarDivider));
+        listView.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground));
+        listView.setDivider(a.getDrawable(R.attr.actionBarDivider));
         a.recycle();
-        layout.setOrientation(LinearLayout.VERTICAL);
-        layout.setDividerPadding(getPixelValue("12dp", metrics));
-        layout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
-
-        ListView listView = new ListView(mActionBar.getPopupContext(), null,
-                R.attr.dropDownListViewStyle);
-        listView.setAdapter(adapter);
-        layout.addView(listView);
-        return layout;
+        listView.setElevation(mActionBar.getMenuPopupElevation());
+        mEnclosingLayout.addView(listView);
     }
 
     private boolean isOverflowPopupNeeded() {
@@ -244,9 +236,30 @@
         return maxWidth;
     }
 
-    private int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) {
+    static int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) {
         TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/);
         return (int) typedValue.getDimension(metrics);
     }
 
+    // TODO: This is duplicated from RenderSessionImpl.
+    private int getActionBarHeight() {
+        RenderResources resources = mBridgeContext.getRenderResources();
+        DisplayMetrics metrics = mBridgeContext.getMetrics();
+        ResourceValue value = resources.findItemInTheme("actionBarSize", true);
+
+        // resolve it
+        value = resources.resolveResValue(value);
+
+        if (value != null) {
+            // get the numerical value, if available
+            TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(),
+                    true);
+            if (typedValue != null) {
+                // compute the pixel value based on the display metrics
+                return (int) typedValue.getDimension(metrics);
+
+            }
+        }
+        return 0;
+    }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java
index 70b9cc3..6db722e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomActionBarWrapper.java
@@ -65,18 +65,17 @@
      * Returns a wrapper around different implementations of the Action Bar to provide a common API.
      *
      * @param decorContent the top level view returned by inflating
-                     * ?attr/windowActionBarFullscreenDecorLayout
+     *                     ?attr/windowActionBarFullscreenDecorLayout
      */
     @NonNull
     public static CustomActionBarWrapper getActionBarWrapper(@NonNull BridgeContext context,
             @NonNull SessionParams params, @NonNull View decorContent) {
         View view = decorContent.findViewById(R.id.action_bar);
         if (view instanceof Toolbar) {
-            return new ToolbarWrapper(context, params, ((Toolbar) view)
-            );
+            return new ToolbarWrapper(context, params, ((Toolbar) view));
         } else if (view instanceof ActionBarView) {
-            return new WindowActionBarWrapper(context, params, decorContent, ((ActionBarView) view)
-            );
+            return new WindowActionBarWrapper(context, params, decorContent,
+                    ((ActionBarView) view));
         } else {
             throw new IllegalStateException("Can't make an action bar out of " +
                     view.getClass().getSimpleName());
@@ -174,6 +173,13 @@
     @NonNull
     abstract DecorToolbar getDecorToolbar();
 
+    abstract int getMenuPopupElevation();
+
+    /**
+     * Margin between the menu popup and the action bar.
+     */
+    abstract int getMenuPopupMargin();
+
     // ---- The implementations ----
 
     /**
@@ -226,25 +232,38 @@
         DecorToolbar getDecorToolbar() {
             return mToolbar.getWrapper();
         }
+
+        @Override
+        int getMenuPopupElevation() {
+            return 10;
+        }
+
+        @Override
+        int getMenuPopupMargin() {
+            return 0;
+        }
     }
 
     /**
      * Holo theme uses {@link WindowDecorActionBar} as the action bar. This wrapper provides
      * access to it using a common API.
      */
-    private static class WindowActionBarWrapper extends CustomActionBarWrapper{
+    private static class WindowActionBarWrapper extends CustomActionBarWrapper {
 
         @NonNull
         private final WindowDecorActionBar mActionBar;
+        @NonNull
         private final ActionBarView mActionBarView;
+        @NonNull
+        private final View mDecorContentRoot;
         private MenuBuilder mMenuBuilder;
 
         public WindowActionBarWrapper(@NonNull BridgeContext context, @NonNull SessionParams params,
                 @NonNull View decorContentRoot, @NonNull ActionBarView actionBarView) {
-            super(context, params, new WindowDecorActionBar(decorContentRoot)
-            );
+            super(context, params, new WindowDecorActionBar(decorContentRoot));
             mActionBarView = actionBarView;
             mActionBar = ((WindowDecorActionBar) super.mActionBar);
+            mDecorContentRoot = decorContentRoot;
         }
 
         @Override
@@ -270,7 +289,7 @@
             }
 
             // Set action bar to be split, if needed.
-            ViewGroup splitView = (ViewGroup) mActionBarView.findViewById(R.id.split_action_bar);
+            ViewGroup splitView = (ViewGroup) mDecorContentRoot.findViewById(R.id.split_action_bar);
             if (splitView != null) {
                 mActionBarView.setSplitView(splitView);
                 Resources res = mContext.getResources();
@@ -314,6 +333,16 @@
             return mActionBarView;
         }
 
+        @Override
+        int getMenuPopupElevation() {
+            return 0;
+        }
+
+        @Override
+        int getMenuPopupMargin() {
+            return -ActionBarLayout.getPixelValue("10dp", mContext.getMetrics());
+        }
+
         // TODO: Use an adapter, like List View to set up tabs.
         @SuppressWarnings("deprecation")  // For Tab
         private void setupTabs(int num) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
index b677131..669e6b5 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderDrawable.java
@@ -57,63 +57,59 @@
 
     public Result render() {
         checkLock();
-        try {
-            // get the drawable resource value
-            DrawableParams params = getParams();
-            HardwareConfig hardwareConfig = params.getHardwareConfig();
-            ResourceValue drawableResource = params.getDrawable();
+        // get the drawable resource value
+        DrawableParams params = getParams();
+        HardwareConfig hardwareConfig = params.getHardwareConfig();
+        ResourceValue drawableResource = params.getDrawable();
 
-            // resolve it
-            BridgeContext context = getContext();
-            drawableResource = context.getRenderResources().resolveResValue(drawableResource);
+        // resolve it
+        BridgeContext context = getContext();
+        drawableResource = context.getRenderResources().resolveResValue(drawableResource);
 
-            if (drawableResource == null ||
-                    drawableResource.getResourceType() != ResourceType.DRAWABLE) {
-                return Status.ERROR_NOT_A_DRAWABLE.createResult();
-            }
-
-            // create a simple FrameLayout
-            FrameLayout content = new FrameLayout(context);
-
-            // get the actual Drawable object to draw
-            Drawable d = ResourceHelper.getDrawable(drawableResource, context);
-            content.setBackground(d);
-
-            // set the AttachInfo on the root view.
-            AttachInfo_Accessor.setAttachInfo(content);
-
-
-            // measure
-            int w = hardwareConfig.getScreenWidth();
-            int h = hardwareConfig.getScreenHeight();
-            int w_spec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY);
-            int h_spec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
-            content.measure(w_spec, h_spec);
-
-            // now do the layout.
-            content.layout(0, 0, w, h);
-
-            // preDraw setup
-            AttachInfo_Accessor.dispatchOnPreDraw(content);
-
-            // draw into a new image
-            BufferedImage image = getImage(w, h);
-
-            // create an Android bitmap around the BufferedImage
-            Bitmap bitmap = Bitmap_Delegate.createBitmap(image,
-                    true /*isMutable*/, hardwareConfig.getDensity());
-
-            // create a Canvas around the Android bitmap
-            Canvas canvas = new Canvas(bitmap);
-            canvas.setDensity(hardwareConfig.getDensity().getDpiValue());
-
-            // and draw
-            content.draw(canvas);
-
-            return Status.SUCCESS.createResult(image);
-        } catch (IOException e) {
-            return ERROR_UNKNOWN.createResult(e.getMessage(), e);
+        if (drawableResource == null ||
+                drawableResource.getResourceType() != ResourceType.DRAWABLE) {
+            return Status.ERROR_NOT_A_DRAWABLE.createResult();
         }
+
+        // create a simple FrameLayout
+        FrameLayout content = new FrameLayout(context);
+
+        // get the actual Drawable object to draw
+        Drawable d = ResourceHelper.getDrawable(drawableResource, context);
+        content.setBackground(d);
+
+        // set the AttachInfo on the root view.
+        AttachInfo_Accessor.setAttachInfo(content);
+
+
+        // measure
+        int w = hardwareConfig.getScreenWidth();
+        int h = hardwareConfig.getScreenHeight();
+        int w_spec = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY);
+        int h_spec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY);
+        content.measure(w_spec, h_spec);
+
+        // now do the layout.
+        content.layout(0, 0, w, h);
+
+        // preDraw setup
+        AttachInfo_Accessor.dispatchOnPreDraw(content);
+
+        // draw into a new image
+        BufferedImage image = getImage(w, h);
+
+        // create an Android bitmap around the BufferedImage
+        Bitmap bitmap = Bitmap_Delegate.createBitmap(image,
+                true /*isMutable*/, hardwareConfig.getDensity());
+
+        // create a Canvas around the Android bitmap
+        Canvas canvas = new Canvas(bitmap);
+        canvas.setDensity(hardwareConfig.getDensity().getDpiValue());
+
+        // and draw
+        content.draw(canvas);
+
+        return Status.SUCCESS.createResult(image);
     }
 
     protected BufferedImage getImage(int w, int h) {
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 2fcdf34..4e6f456 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -162,6 +162,11 @@
         "android.view.WindowManagerGlobal#getWindowManagerService",
         "android.view.inputmethod.InputMethodManager#getInstance",
         "android.view.MenuInflater#registerMenu",
+        "android.view.RenderNode#nCreate",
+        "android.view.RenderNode#nDestroyRenderNode",
+        "android.view.RenderNode#nSetElevation",
+        "android.view.RenderNode#nGetElevation",
+        "android.view.ViewGroup#drawChild",
         "com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
         "com.android.internal.util.XmlUtils#convertValueToInt",
         "com.android.internal.textservice.ITextServicesManager$Stub#asInterface",
diff --git a/tools/split-select/Android.mk b/tools/split-select/Android.mk
index dc48ea8..968d22b 100644
--- a/tools/split-select/Android.mk
+++ b/tools/split-select/Android.mk
@@ -34,7 +34,8 @@
 testSources := \
     Grouper_test.cpp \
     Rule_test.cpp \
-    RuleGenerator_test.cpp
+    RuleGenerator_test.cpp \
+    TestRules.cpp
 
 cIncludes := \
     external/zlib \
diff --git a/tools/split-select/Rule.cpp b/tools/split-select/Rule.cpp
index 9559fe2..48d21ff 100644
--- a/tools/split-select/Rule.cpp
+++ b/tools/split-select/Rule.cpp
@@ -29,6 +29,16 @@
     }
 }
 
+Rule::Rule(const Rule& rhs)
+    : RefBase()
+    , op(rhs.op)
+    , key(rhs.key)
+    , negate(rhs.negate)
+    , stringArgs(rhs.stringArgs)
+    , longArgs(rhs.longArgs)
+    , subrules(rhs.subrules) {
+}
+
 String8 Rule::toJson(int indent) const {
     String8 str;
     indentStr(str, indent);
diff --git a/tools/split-select/Rule.h b/tools/split-select/Rule.h
index 8029931..08a2075 100644
--- a/tools/split-select/Rule.h
+++ b/tools/split-select/Rule.h
@@ -28,6 +28,7 @@
 
 struct Rule : public virtual android::RefBase {
     inline Rule();
+    Rule(const Rule& rhs);
 
     enum Operator {
         LESS_THAN = 1,
diff --git a/tools/split-select/RuleGenerator.cpp b/tools/split-select/RuleGenerator.cpp
index b8f3bcb..72bb0c7 100644
--- a/tools/split-select/RuleGenerator.cpp
+++ b/tools/split-select/RuleGenerator.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "RuleGenerator.h"
+#include "aapt/SdkConstants.h"
 
 #include <algorithm>
 #include <cmath>
@@ -32,18 +33,21 @@
 }
 
 sp<Rule> RuleGenerator::generateDensity(const Vector<int>& allDensities, size_t index) {
-    sp<Rule> densityRule = new Rule();
-    densityRule->op = Rule::AND_SUBRULES;
+    if (allDensities[index] != ResTable_config::DENSITY_ANY) {
+        sp<Rule> densityRule = new Rule();
+        densityRule->op = Rule::AND_SUBRULES;
 
-    const bool anyDensity = allDensities[index] == ResTable_config::DENSITY_ANY;
-    sp<Rule> any = new Rule();
-    any->op = Rule::EQUALS;
-    any->key = Rule::SCREEN_DENSITY;
-    any->longArgs.add((int)ResTable_config::DENSITY_ANY);
-    any->negate = !anyDensity;
-    densityRule->subrules.add(any);
+        const bool hasAnyDensity = std::find(allDensities.begin(),
+                allDensities.end(), ResTable_config::DENSITY_ANY) != allDensities.end();
 
-    if (!anyDensity) {
+        if (hasAnyDensity) {
+            sp<Rule> version = new Rule();
+            version->op = Rule::LESS_THAN;
+            version->key = Rule::SDK_VERSION;
+            version->longArgs.add((long) SDK_LOLLIPOP);
+            densityRule->subrules.add(version);
+        }
+
         if (index > 0) {
             sp<Rule> gt = new Rule();
             gt->op = Rule::GREATER_THAN;
@@ -59,8 +63,14 @@
             lt->longArgs.add(findMid(allDensities[index], allDensities[index + 1]));
             densityRule->subrules.add(lt);
         }
+        return densityRule;
+    } else {
+        // SDK_VERSION is handled elsewhere, so we always pick DENSITY_ANY if it's
+        // available.
+        sp<Rule> always = new Rule();
+        always->op = Rule::ALWAYS_TRUE;
+        return always;
     }
-    return densityRule;
 }
 
 sp<Rule> RuleGenerator::generateAbi(const Vector<abi::Variant>& splitAbis, size_t index) {
diff --git a/tools/split-select/RuleGenerator_test.cpp b/tools/split-select/RuleGenerator_test.cpp
index ee387be..778d604 100644
--- a/tools/split-select/RuleGenerator_test.cpp
+++ b/tools/split-select/RuleGenerator_test.cpp
@@ -16,154 +16,95 @@
 
 #include "RuleGenerator.h"
 
-#include <algorithm>
+#include "aapt/SdkConstants.h"
+#include "TestRules.h"
+
 #include <gtest/gtest.h>
-#include <utils/String8.h>
+#include <utils/Vector.h>
 
 using namespace android;
+using namespace split::test;
 
 namespace split {
 
-static void expectDensityRule(const Vector<int>& densities, int density, int greaterThan, int lessThan);
-static void expectAbiRule(const Vector<abi::Variant>& abis, abi::Variant variant, const char* a);
-static void expectAbiRule(const Vector<abi::Variant>& abis, abi::Variant variant, const char* a, const char* b);
-
 TEST(RuleGeneratorTest, testAbiRules) {
     Vector<abi::Variant> abis;
-    abis.add(abi::Variant_armeabi);
-    abis.add(abi::Variant_armeabi_v7a);
-    abis.add(abi::Variant_x86);
-    std::sort(abis.begin(), abis.end());
+    const ssize_t armeabiIndex = abis.add(abi::Variant_armeabi);
+    const ssize_t armeabi_v7aIndex = abis.add(abi::Variant_armeabi_v7a);
+    const ssize_t x86Index = abis.add(abi::Variant_x86);
 
-    expectAbiRule(abis, abi::Variant_armeabi, "armeabi");
-    expectAbiRule(abis, abi::Variant_armeabi_v7a, "armeabi-v7a", "arm64-v8a");
-    expectAbiRule(abis, abi::Variant_x86, "x86", "x86_64");
+    EXPECT_RULES_EQ(RuleGenerator::generateAbi(abis, armeabiIndex),
+            ContainsAnyRule(Rule::NATIVE_PLATFORM, "armeabi")
+    );
+
+    EXPECT_RULES_EQ(RuleGenerator::generateAbi(abis, armeabi_v7aIndex),
+            ContainsAnyRule(Rule::NATIVE_PLATFORM, "armeabi-v7a", "arm64-v8a")
+    );
+
+    EXPECT_RULES_EQ(RuleGenerator::generateAbi(abis, x86Index),
+            ContainsAnyRule(Rule::NATIVE_PLATFORM, "x86", "x86_64")
+    );
+}
+
+TEST(RuleGeneratorTest, densityConstantsAreSane) {
+    EXPECT_LT(263, ConfigDescription::DENSITY_XHIGH);
+    EXPECT_GT(262, ConfigDescription::DENSITY_HIGH);
+    EXPECT_LT(363, ConfigDescription::DENSITY_XXHIGH);
+    EXPECT_GT(362, ConfigDescription::DENSITY_XHIGH);
 }
 
 TEST(RuleGeneratorTest, testDensityRules) {
     Vector<int> densities;
-    densities.add(ConfigDescription::DENSITY_HIGH);
-    densities.add(ConfigDescription::DENSITY_XHIGH);
-    densities.add(ConfigDescription::DENSITY_XXHIGH);
-    densities.add(ConfigDescription::DENSITY_ANY);
+    const ssize_t highIndex = densities.add(ConfigDescription::DENSITY_HIGH);
+    const ssize_t xhighIndex = densities.add(ConfigDescription::DENSITY_XHIGH);
+    const ssize_t xxhighIndex = densities.add(ConfigDescription::DENSITY_XXHIGH);
 
-    ASSERT_LT(263, ConfigDescription::DENSITY_XHIGH);
-    ASSERT_GT(262, ConfigDescription::DENSITY_HIGH);
-    ASSERT_LT(363, ConfigDescription::DENSITY_XXHIGH);
-    ASSERT_GT(362, ConfigDescription::DENSITY_XHIGH);
+    EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, highIndex),
+            AndRule()
+            .add(LtRule(Rule::SCREEN_DENSITY, 263))
+    );
 
-    expectDensityRule(densities, ConfigDescription::DENSITY_HIGH, 0, 263);
-    expectDensityRule(densities, ConfigDescription::DENSITY_XHIGH, 262, 363);
-    expectDensityRule(densities, ConfigDescription::DENSITY_XXHIGH, 362, 0);
-    expectDensityRule(densities, ConfigDescription::DENSITY_ANY, 0, 0);
+    EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, xhighIndex),
+            AndRule()
+            .add(GtRule(Rule::SCREEN_DENSITY, 262))
+            .add(LtRule(Rule::SCREEN_DENSITY, 363))
+    );
+
+    EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, xxhighIndex),
+            AndRule()
+            .add(GtRule(Rule::SCREEN_DENSITY, 362))
+    );
 }
 
-//
-// Helper methods.
-//
+TEST(RuleGeneratorTest, testDensityRulesWithAnyDpi) {
+    Vector<int> densities;
+    const ssize_t highIndex = densities.add(ConfigDescription::DENSITY_HIGH);
+    const ssize_t xhighIndex = densities.add(ConfigDescription::DENSITY_XHIGH);
+    const ssize_t xxhighIndex = densities.add(ConfigDescription::DENSITY_XXHIGH);
+    const ssize_t anyIndex = densities.add(ConfigDescription::DENSITY_ANY);
 
-static void expectDensityRule(const Vector<int>& densities, int density, int greaterThan, int lessThan) {
-    const int* iter = std::find(densities.begin(), densities.end(), density);
-    if (densities.end() == iter) {
-        ADD_FAILURE() << density << "dpi was not in the density list.";
-        return;
-    }
+    EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, highIndex),
+            AndRule()
+            .add(LtRule(Rule::SDK_VERSION, SDK_LOLLIPOP))
+            .add(LtRule(Rule::SCREEN_DENSITY, 263))
+    );
 
-    sp<Rule> rule = RuleGenerator::generateDensity(densities, iter - densities.begin());
-    if (rule->op != Rule::AND_SUBRULES) {
-        ADD_FAILURE() << "Op in rule for " << density << "dpi is not Rule::AND_SUBRULES.";
-        return;
-    }
+    EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, xhighIndex),
+            AndRule()
+            .add(LtRule(Rule::SDK_VERSION, SDK_LOLLIPOP))
+            .add(GtRule(Rule::SCREEN_DENSITY, 262))
+            .add(LtRule(Rule::SCREEN_DENSITY, 363))
+    );
 
-    size_t index = 0;
+    EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, xxhighIndex),
+            AndRule()
+            .add(LtRule(Rule::SDK_VERSION, SDK_LOLLIPOP))
+            .add(GtRule(Rule::SCREEN_DENSITY, 362))
+    );
 
-    bool isAnyDpi = density == ConfigDescription::DENSITY_ANY;
-
-    sp<Rule> anyDpiRule = rule->subrules[index++];
-    EXPECT_EQ(Rule::EQUALS, anyDpiRule->op)
-            << "for " << density << "dpi ANY DPI rule";
-    EXPECT_EQ(Rule::SCREEN_DENSITY, anyDpiRule->key)
-            << "for " << density << "dpi ANY DPI rule";
-    EXPECT_EQ(isAnyDpi == false, anyDpiRule->negate)
-            << "for " << density << "dpi ANY DPI rule";
-    if (anyDpiRule->longArgs.size() == 1) {
-        EXPECT_EQ(ConfigDescription::DENSITY_ANY, anyDpiRule->longArgs[0])
-            << "for " << density << "dpi ANY DPI rule";
-    } else {
-        EXPECT_EQ(1u, anyDpiRule->longArgs.size())
-            << "for " << density << "dpi ANY DPI rule";
-    }
-
-
-    if (greaterThan != 0) {
-        sp<Rule> greaterThanRule = rule->subrules[index++];
-        EXPECT_EQ(Rule::GREATER_THAN, greaterThanRule->op)
-                << "for " << density << "dpi GREATER_THAN rule";
-        EXPECT_EQ(Rule::SCREEN_DENSITY, greaterThanRule->key)
-                << "for " << density << "dpi GREATER_THAN rule";
-        if (greaterThanRule->longArgs.size() == 1) {
-            EXPECT_EQ(greaterThan, greaterThanRule->longArgs[0])
-                << "for " << density << "dpi GREATER_THAN rule";
-        } else {
-            EXPECT_EQ(1u, greaterThanRule->longArgs.size())
-                << "for " << density << "dpi GREATER_THAN rule";
-        }
-    }
-
-    if (lessThan != 0) {
-        sp<Rule> lessThanRule = rule->subrules[index++];
-        EXPECT_EQ(Rule::LESS_THAN, lessThanRule->op)
-                << "for " << density << "dpi LESS_THAN rule";
-        EXPECT_EQ(Rule::SCREEN_DENSITY, lessThanRule->key)
-                << "for " << density << "dpi LESS_THAN rule";
-        if (lessThanRule->longArgs.size() == 1) {
-            EXPECT_EQ(lessThan, lessThanRule->longArgs[0])
-                << "for " << density << "dpi LESS_THAN rule";
-        } else {
-            EXPECT_EQ(1u, lessThanRule->longArgs.size())
-                << "for " << density << "dpi LESS_THAN rule";
-        }
-    }
-}
-
-static void expectAbiRule(const Vector<abi::Variant>& abis, abi::Variant variant, const Vector<const char*>& matches) {
-    const abi::Variant* iter = std::find(abis.begin(), abis.end(), variant);
-    if (abis.end() == iter) {
-        ADD_FAILURE() << abi::toString(variant) << " was not in the abi list.";
-        return;
-    }
-
-    sp<Rule> rule = RuleGenerator::generateAbi(abis, iter - abis.begin());
-
-    EXPECT_EQ(Rule::CONTAINS_ANY, rule->op)
-            << "for " << abi::toString(variant) << " rule";
-    EXPECT_EQ(Rule::NATIVE_PLATFORM, rule->key)
-            << " for " << abi::toString(variant) << " rule";
-    EXPECT_EQ(matches.size(), rule->stringArgs.size())
-            << " for " << abi::toString(variant) << " rule";
-
-    const size_t matchCount = matches.size();
-    for (size_t i = 0; i < matchCount; i++) {
-        const char* match = matches[i];
-        if (rule->stringArgs.end() ==
-                std::find(rule->stringArgs.begin(), rule->stringArgs.end(), String8(match))) {
-            ADD_FAILURE() << "Rule for abi " << abi::toString(variant)
-                    << " does not contain match for expected abi " << match;
-        }
-    }
-}
-
-static void expectAbiRule(const Vector<abi::Variant>& abis, abi::Variant variant, const char* a) {
-    Vector<const char*> matches;
-    matches.add(a);
-    expectAbiRule(abis, variant, matches);
-}
-
-static void expectAbiRule(const Vector<abi::Variant>& abis, abi::Variant variant, const char* a, const char* b) {
-    Vector<const char*> matches;
-    matches.add(a);
-    matches.add(b);
-    expectAbiRule(abis, variant, matches);
+    // We expect AlwaysTrue because anydpi always has attached v21 to the configuration
+    // and the rest of the rule generation code generates the sdk version checks.
+    EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, anyIndex), AlwaysTrue());
 }
 
 } // namespace split
diff --git a/tools/split-select/Rule_test.cpp b/tools/split-select/Rule_test.cpp
index aca7433..c6cff0d 100644
--- a/tools/split-select/Rule_test.cpp
+++ b/tools/split-select/Rule_test.cpp
@@ -17,42 +17,28 @@
 #include "Rule.h"
 
 #include "SplitDescription.h"
+#include "TestRules.h"
 
 #include <algorithm>
-#include <string>
 #include <gtest/gtest.h>
+#include <string>
 #include <utils/String8.h>
 
 using namespace android;
+using namespace split::test;
 
 namespace split {
 
 TEST(RuleTest, generatesValidJson) {
-    sp<Rule> rule = new Rule();
-    rule->op = Rule::AND_SUBRULES;
+    Rule rule(AndRule()
+        .add(EqRule(Rule::SDK_VERSION, 7))
+        .add(OrRule()
+                .add(GtRule(Rule::SCREEN_DENSITY, 10))
+                .add(LtRule(Rule::SCREEN_DENSITY, 5))
+        )
+    );
 
-    sp<Rule> subrule = new Rule();
-    subrule->op = Rule::EQUALS;
-    subrule->key = Rule::SDK_VERSION;
-    subrule->longArgs.add(7);
-    rule->subrules.add(subrule);
-
-    subrule = new Rule();
-    subrule->op = Rule::OR_SUBRULES;
-    rule->subrules.add(subrule);
-
-    sp<Rule> subsubrule = new Rule();
-    subsubrule->op = Rule::GREATER_THAN;
-    subsubrule->key = Rule::SCREEN_DENSITY;
-    subsubrule->longArgs.add(10);
-    subrule->subrules.add(subsubrule);
-
-    subsubrule = new Rule();
-    subsubrule->op = Rule::LESS_THAN;
-    subsubrule->key = Rule::SCREEN_DENSITY;
-    subsubrule->longArgs.add(5);
-    subrule->subrules.add(subsubrule);
-
+    // Expected
     std::string expected(
             "{"
             "  \"op\": \"AND_SUBRULES\","
@@ -79,69 +65,36 @@
             "     }"
             "  ]"
             "}");
-    // Trim
     expected.erase(std::remove_if(expected.begin(), expected.end(), ::isspace), expected.end());
 
-    std::string result(rule->toJson().string());
-
-    // Trim
+    // Result
+    std::string result(rule.toJson().string());
     result.erase(std::remove_if(result.begin(), result.end(), ::isspace), result.end());
 
     ASSERT_EQ(expected, result);
 }
 
 TEST(RuleTest, simplifiesSingleSubruleRules) {
-    sp<Rule> rule = new Rule();
-    rule->op = Rule::AND_SUBRULES;
+    sp<Rule> rule = new Rule(AndRule()
+        .add(EqRule(Rule::SDK_VERSION, 7))
+    );
 
-    sp<Rule> subrule = new Rule();
-    subrule->op = Rule::EQUALS;
-    subrule->key = Rule::SDK_VERSION;
-    subrule->longArgs.add(7);
-    rule->subrules.add(subrule);
-
-    sp<Rule> simplified = Rule::simplify(rule);
-    EXPECT_EQ(Rule::EQUALS, simplified->op);
-    EXPECT_EQ(Rule::SDK_VERSION, simplified->key);
-    ASSERT_EQ(1u, simplified->longArgs.size());
-    EXPECT_EQ(7, simplified->longArgs[0]);
+    EXPECT_RULES_EQ(Rule::simplify(rule), EqRule(Rule::SDK_VERSION, 7));
 }
 
 TEST(RuleTest, simplifiesNestedSameOpSubrules) {
-    sp<Rule> rule = new Rule();
-    rule->op = Rule::AND_SUBRULES;
+    sp<Rule> rule = new Rule(AndRule()
+        .add(AndRule()
+            .add(EqRule(Rule::SDK_VERSION, 7))
+        )
+        .add(EqRule(Rule::SDK_VERSION, 8))
+    );
 
-    sp<Rule> subrule = new Rule();
-    subrule->op = Rule::AND_SUBRULES;
-    rule->subrules.add(subrule);
-
-    sp<Rule> subsubrule = new Rule();
-    subsubrule->op = Rule::EQUALS;
-    subsubrule->key = Rule::SDK_VERSION;
-    subsubrule->longArgs.add(7);
-    subrule->subrules.add(subsubrule);
-
-    subrule = new Rule();
-    subrule->op = Rule::EQUALS;
-    subrule->key = Rule::SDK_VERSION;
-    subrule->longArgs.add(8);
-    rule->subrules.add(subrule);
-
-    sp<Rule> simplified = Rule::simplify(rule);
-    EXPECT_EQ(Rule::AND_SUBRULES, simplified->op);
-    ASSERT_EQ(2u, simplified->subrules.size());
-
-    sp<Rule> simplifiedSubrule = simplified->subrules[0];
-    EXPECT_EQ(Rule::EQUALS, simplifiedSubrule->op);
-    EXPECT_EQ(Rule::SDK_VERSION, simplifiedSubrule->key);
-    ASSERT_EQ(1u, simplifiedSubrule->longArgs.size());
-    EXPECT_EQ(7, simplifiedSubrule->longArgs[0]);
-
-    simplifiedSubrule = simplified->subrules[1];
-    EXPECT_EQ(Rule::EQUALS, simplifiedSubrule->op);
-    EXPECT_EQ(Rule::SDK_VERSION, simplifiedSubrule->key);
-    ASSERT_EQ(1u, simplifiedSubrule->longArgs.size());
-    EXPECT_EQ(8, simplifiedSubrule->longArgs[0]);
+    EXPECT_RULES_EQ(Rule::simplify(rule),
+            AndRule()
+                .add(EqRule(Rule::SDK_VERSION, 7))
+                .add(EqRule(Rule::SDK_VERSION, 8))
+    );
 }
 
 } // namespace split
diff --git a/tools/split-select/TestRules.cpp b/tools/split-select/TestRules.cpp
new file mode 100644
index 0000000..f980dc4
--- /dev/null
+++ b/tools/split-select/TestRules.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 "TestRules.h"
+
+#include <utils/String8.h>
+
+using android::String8;
+using android::sp;
+
+namespace split {
+namespace test {
+
+const Rule EqRule(Rule::Key key, long value) {
+    Rule rule;
+    rule.op = Rule::EQUALS;
+    rule.key = key;
+    rule.longArgs.add(value);
+    return rule;
+}
+
+const Rule GtRule(Rule::Key key, long value) {
+    Rule rule;
+    rule.op = Rule::GREATER_THAN;
+    rule.key = key;
+    rule.longArgs.add(value);
+    return rule;
+}
+
+const Rule LtRule(Rule::Key key, long value) {
+    Rule rule;
+    rule.op = Rule::LESS_THAN;
+    rule.key = key;
+    rule.longArgs.add(value);
+    return rule;
+}
+
+const Rule ContainsAnyRule(Rule::Key key, const char* str1) {
+    Rule rule;
+    rule.op = Rule::CONTAINS_ANY;
+    rule.key = key;
+    rule.stringArgs.add(String8(str1));
+    return rule;
+}
+
+const Rule ContainsAnyRule(Rule::Key key, const char* str1, const char* str2) {
+    Rule rule;
+    rule.op = Rule::CONTAINS_ANY;
+    rule.key = key;
+    rule.stringArgs.add(String8(str1));
+    rule.stringArgs.add(String8(str2));
+    return rule;
+}
+
+const Rule AlwaysTrue() {
+    Rule rule;
+    rule.op = Rule::ALWAYS_TRUE;
+    return rule;
+}
+
+::testing::AssertionResult RulePredFormat(
+        const char*, const char*,
+        const sp<Rule>& actual, const Rule& expected) {
+    const String8 expectedStr(expected.toJson());
+    const String8 actualStr(actual != NULL ? actual->toJson() : "");
+
+    if (expectedStr != actualStr) {
+        return ::testing::AssertionFailure()
+                << "Expected: " << expectedStr.string() << "\n"
+                << "  Actual: " << actualStr.string();
+    }
+    return ::testing::AssertionSuccess();
+}
+
+
+} // namespace test
+} // namespace split
diff --git a/tools/split-select/TestRules.h b/tools/split-select/TestRules.h
new file mode 100644
index 0000000..50b7ad1
--- /dev/null
+++ b/tools/split-select/TestRules.h
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#ifndef H_AAPT_SPLIT_TEST_RULES
+#define H_AAPT_SPLIT_TEST_RULES
+
+#include "Rule.h"
+
+#include <gtest/gtest.h>
+
+namespace split {
+namespace test {
+
+struct AndRule : public Rule {
+    AndRule() {
+        op = Rule::AND_SUBRULES;
+    }
+
+    AndRule& add(const Rule& rhs) {
+        subrules.add(new Rule(rhs));
+        return *this;
+    }
+};
+
+struct OrRule : public Rule {
+    OrRule() {
+        op = Rule::OR_SUBRULES;
+    }
+
+    OrRule& add(const Rule& rhs) {
+        subrules.add(new Rule(rhs));
+        return *this;
+    }
+};
+
+const Rule EqRule(Rule::Key key, long value);
+const Rule LtRule(Rule::Key key, long value);
+const Rule GtRule(Rule::Key key, long value);
+const Rule ContainsAnyRule(Rule::Key key, const char* str1);
+const Rule ContainsAnyRule(Rule::Key key, const char* str1, const char* str2);
+const Rule AlwaysTrue();
+
+::testing::AssertionResult RulePredFormat(
+        const char* actualExpr, const char* expectedExpr,
+        const android::sp<Rule>& actual, const Rule& expected);
+
+#define EXPECT_RULES_EQ(actual, expected) \
+        EXPECT_PRED_FORMAT2(::split::test::RulePredFormat, actual, expected)
+
+} // namespace test
+} // namespace split
+
+#endif // H_AAPT_SPLIT_TEST_RULES