Merge "Don't use overlay to transition ImageViews."
diff --git a/Android.mk b/Android.mk
index 8ba8815..bb65398 100644
--- a/Android.mk
+++ b/Android.mk
@@ -306,6 +306,7 @@
media/java/android/media/IRemoteControlDisplay.aidl \
media/java/android/media/IRemoteDisplayCallback.aidl \
media/java/android/media/IRemoteDisplayProvider.aidl \
+ media/java/android/media/IRemoteVolumeController.aidl \
media/java/android/media/IRemoteVolumeObserver.aidl \
media/java/android/media/IRingtonePlayer.aidl \
media/java/android/media/IVolumeController.aidl \
@@ -330,9 +331,18 @@
telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl \
telecomm/java/com/android/internal/telecomm/ICallServiceLookupResponse.aidl \
telecomm/java/com/android/internal/telecomm/ICallServiceProvider.aidl \
+ telecomm/java/com/android/internal/telecomm/ICallVideoProvider.aidl \
telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl \
telecomm/java/com/android/internal/telecomm/IInCallService.aidl \
telecomm/java/com/android/internal/telecomm/ITelecommService.aidl \
+ telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl \
+ telephony/java/com/android/ims/internal/IImsCallSession.aidl \
+ telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl \
+ telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl \
+ telephony/java/com/android/ims/internal/IImsService.aidl \
+ telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl \
+ telephony/java/com/android/ims/internal/IImsUt.aidl \
+ telephony/java/com/android/ims/internal/IImsUtListener.aidl \
telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \
telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
telephony/java/com/android/internal/telephony/ITelephony.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index b6a47f2..73f370d 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -203,6 +203,8 @@
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/classes/android/app/maintenance)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates/src/android/app/maintenance)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/services_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
diff --git a/api/current.txt b/api/current.txt
index e541c66..117d5fe 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -114,6 +114,7 @@
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 RECOVERY = "android.permission.RECOVERY";
+ field public static final java.lang.String REMOVE_VOICEMAIL = "com.android.voicemail.permission.REMOVE_VOICEMAIL";
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";
@@ -1309,6 +1310,7 @@
field public static final int viewportHeight = 16843805; // 0x101041d
field public static final int viewportWidth = 16843804; // 0x101041c
field public static final int visibility = 16842972; // 0x10100dc
+ field public static final int visibilityMode = 16843902; // 0x101047e
field public static final int visible = 16843156; // 0x1010194
field public static final int vmSafeMode = 16843448; // 0x10102b8
field public static final int voiceLanguage = 16843349; // 0x1010255
@@ -12516,6 +12518,7 @@
field public static final int CONTROL_SCENE_MODE_DISABLED = 0; // 0x0
field public static final int CONTROL_SCENE_MODE_FACE_PRIORITY = 1; // 0x1
field public static final int CONTROL_SCENE_MODE_FIREWORKS = 12; // 0xc
+ field public static final int CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO = 17; // 0x11
field public static final int CONTROL_SCENE_MODE_LANDSCAPE = 4; // 0x4
field public static final int CONTROL_SCENE_MODE_NIGHT = 5; // 0x5
field public static final int CONTROL_SCENE_MODE_NIGHT_PORTRAIT = 6; // 0x6
@@ -14467,7 +14470,7 @@
method public java.util.List<byte[]> getSecureStops();
method public static final boolean isCryptoSchemeSupported(java.util.UUID);
method public static final boolean isCryptoSchemeSupported(java.util.UUID, java.lang.String);
- method public byte[] openSession() throws android.media.NotProvisionedException;
+ method public byte[] openSession() throws android.media.NotProvisionedException, android.media.ResourceBusyException;
method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException;
method public void provideProvisionResponse(byte[]) throws android.media.DeniedByServerException;
method public java.util.HashMap<java.lang.String, java.lang.String> queryKeyStatus(byte[]);
@@ -15750,6 +15753,7 @@
public final class MediaController {
method public void addCallback(android.media.session.MediaController.Callback);
method public void addCallback(android.media.session.MediaController.Callback, android.os.Handler);
+ method public void adjustVolumeBy(int, int);
method public boolean dispatchMediaButtonEvent(android.view.KeyEvent);
method public static android.media.session.MediaController fromToken(android.media.session.MediaSessionToken);
method public android.media.MediaMetadata getMetadata();
@@ -15759,6 +15763,7 @@
method public android.media.session.MediaController.VolumeInfo getVolumeInfo();
method public void removeCallback(android.media.session.MediaController.Callback);
method public void sendControlCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+ method public void setVolumeTo(int, int);
}
public static abstract class MediaController.Callback {
@@ -15766,6 +15771,7 @@
method public void onMetadataChanged(android.media.MediaMetadata);
method public void onPlaybackStateChanged(android.media.session.PlaybackState);
method public void onSessionEvent(java.lang.String, android.os.Bundle);
+ method public void onVolumeInfoChanged(android.media.session.MediaController.VolumeInfo);
}
public final class MediaController.TransportControls {
@@ -15808,8 +15814,8 @@
method public void setPlaybackToRemote(android.media.VolumeProvider);
field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
- field public static final int VOLUME_TYPE_LOCAL = 1; // 0x1
- field public static final int VOLUME_TYPE_REMOTE = 2; // 0x2
+ field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
}
public static abstract class MediaSession.Callback {
@@ -23191,6 +23197,7 @@
field public static final java.lang.String AUTHORITY = "com.android.contacts";
field public static final android.net.Uri AUTHORITY_URI;
field public static final java.lang.String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
+ field public static final java.lang.String DEFERRED_SNIPPETING = "deferred_snippeting";
field public static final java.lang.String DIRECTORY_PARAM_KEY = "directory";
field public static final java.lang.String LIMIT_PARAM_KEY = "limit";
field public static final java.lang.String PRIMARY_ACCOUNT_NAME = "name_for_primary_account";
@@ -23494,6 +23501,7 @@
method public static java.io.InputStream openContactPhotoInputStream(android.content.ContentResolver, android.net.Uri, boolean);
method public static java.io.InputStream openContactPhotoInputStream(android.content.ContentResolver, android.net.Uri);
field public static final android.net.Uri CONTENT_FILTER_URI;
+ field public static final android.net.Uri CONTENT_FREQUENT_URI;
field public static final android.net.Uri CONTENT_GROUP_URI;
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact";
field public static final android.net.Uri CONTENT_LOOKUP_URI;
@@ -23539,6 +23547,7 @@
field public static final java.lang.String IN_VISIBLE_GROUP = "in_visible_group";
field public static final java.lang.String IS_USER_PROFILE = "is_user_profile";
field public static final java.lang.String LOOKUP_KEY = "lookup";
+ field public static final java.lang.String NAME_RAW_CONTACT_ID = "name_raw_contact_id";
field public static final java.lang.String PHOTO_FILE_ID = "photo_file_id";
field public static final java.lang.String PHOTO_ID = "photo_id";
field public static final java.lang.String PHOTO_THUMBNAIL_URI = "photo_thumb_uri";
@@ -23574,6 +23583,7 @@
field public static final java.lang.String IS_SUPER_PRIMARY = "is_super_primary";
field public static final java.lang.String MIMETYPE = "mimetype";
field public static final java.lang.String RAW_CONTACT_ID = "raw_contact_id";
+ field public static final java.lang.String RES_PACKAGE = "res_package";
field public static final java.lang.String SYNC1 = "data_sync1";
field public static final java.lang.String SYNC2 = "data_sync2";
field public static final java.lang.String SYNC3 = "data_sync3";
@@ -23677,11 +23687,13 @@
field public static final java.lang.String GROUP_IS_READ_ONLY = "group_is_read_only";
field public static final java.lang.String GROUP_VISIBLE = "group_visible";
field public static final java.lang.String NOTES = "notes";
+ field public static final java.lang.String RES_PACKAGE = "res_package";
field public static final java.lang.String SHOULD_SYNC = "should_sync";
field public static final java.lang.String SUMMARY_COUNT = "summ_count";
field public static final java.lang.String SUMMARY_WITH_PHONES = "summ_phones";
field public static final java.lang.String SYSTEM_ID = "system_id";
field public static final java.lang.String TITLE = "title";
+ field public static final java.lang.String TITLE_RES = "title_res";
}
public static final class ContactsContract.Intents {
@@ -23834,10 +23846,12 @@
}
protected static abstract interface ContactsContract.RawContactsColumns {
+ field public static final java.lang.String ACCOUNT_TYPE_AND_DATA_SET = "account_type_and_data_set";
field public static final java.lang.String AGGREGATION_MODE = "aggregation_mode";
field public static final java.lang.String CONTACT_ID = "contact_id";
field public static final java.lang.String DATA_SET = "data_set";
field public static final java.lang.String DELETED = "deleted";
+ field public static final java.lang.String NAME_VERIFIED = "name_verified";
field public static final java.lang.String RAW_CONTACT_IS_READ_ONLY = "raw_contact_is_read_only";
field public static final java.lang.String RAW_CONTACT_IS_USER_PROFILE = "raw_contact_is_user_profile";
}
@@ -23849,6 +23863,13 @@
field public static final android.net.Uri PROFILE_CONTENT_URI;
}
+ public static class ContactsContract.SearchSnippetColumns {
+ ctor public ContactsContract.SearchSnippetColumns();
+ field public static final java.lang.String DEFERRED_SNIPPETING_KEY = "deferred_snippeting";
+ field public static final java.lang.String SNIPPET = "snippet";
+ field public static final java.lang.String SNIPPET_ARGS_PARAM_KEY = "snippet_args";
+ }
+
public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/setting";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/setting";
@@ -25767,7 +25788,9 @@
public class ScriptC extends android.renderscript.Script {
ctor protected ScriptC(int, android.renderscript.RenderScript);
+ ctor protected ScriptC(long, android.renderscript.RenderScript);
ctor protected ScriptC(android.renderscript.RenderScript, android.content.res.Resources, int);
+ ctor protected ScriptC(android.renderscript.RenderScript, java.lang.String, byte[], byte[]);
}
public final class ScriptGroup extends android.renderscript.BaseObj {
@@ -27595,8 +27618,10 @@
method public abstract void unhold(java.lang.String);
}
- public final class CallServiceAdapter {
+ public final class CallServiceAdapter implements android.os.IBinder.DeathRecipient {
method public void addConferenceCall(java.lang.String);
+ method public void binderDied();
+ method public void cancelOutgoingCall(java.lang.String);
method public void handleFailedOutgoingCall(android.telecomm.ConnectionRequest, int, java.lang.String);
method public void handleSuccessfulOutgoingCall(java.lang.String);
method public void handoffCall(java.lang.String);
@@ -27652,16 +27677,21 @@
enum_constant public static final android.telecomm.CallState RINGING;
}
+ public class CallVideoProviderWrapper implements android.os.IBinder.DeathRecipient {
+ method public void binderDied();
+ method public void setCamera(java.lang.String) throws android.os.RemoteException;
+ }
+
public abstract class Connection {
ctor protected Connection();
- method public final void conference();
method public final android.telecomm.CallAudioState getCallAudioState();
- method public java.util.List<android.telecomm.Connection> getChildConnections();
+ method public final java.util.List<android.telecomm.Connection> getChildConnections();
method public final android.net.Uri getHandle();
- method public android.telecomm.Connection getParentConnection();
- method public boolean isConferenceCapable();
- method public boolean isConferenceConnection();
- method public boolean isRequestingRingback();
+ method public final android.telecomm.Connection getParentConnection();
+ method public final int getState();
+ method public final boolean isConferenceCapable();
+ method public final boolean isConferenceConnection();
+ method public final boolean isRequestingRingback();
method protected void onAbort();
method protected void onAnswer();
method protected void onChildrenChanged(java.util.List<android.telecomm.Connection>);
@@ -27673,21 +27703,20 @@
method protected void onReject();
method protected void onSeparate();
method protected void onSetAudioState(android.telecomm.CallAudioState);
- method protected void onSetSignal(android.os.Bundle);
method protected void onSetState(int);
method protected void onStopDtmfTone();
method protected void onUnhold();
- method protected void setActive();
- method public void setAudioState(android.telecomm.CallAudioState);
- method protected void setDestroyed();
- method protected void setDialing();
- method protected void setDisconnected(int, java.lang.String);
- method protected void setHandle(android.net.Uri);
- method protected void setIsConferenceCapable(boolean);
- method protected void setOnHold();
- method public void setParentConnection(android.telecomm.Connection);
- method protected void setRequestingRingback(boolean);
- method protected void setRinging();
+ method public final void setActive();
+ method public final void setDestroyed();
+ method public final void setDialing();
+ method public final void setDisconnected(int, java.lang.String);
+ method public final void setHandle(android.net.Uri);
+ method public final void setIsConferenceCapable(boolean);
+ method public final void setOnHold();
+ method public final void setParentConnection(android.telecomm.Connection);
+ method public final void setRequestingRingback(boolean);
+ method public final void setRinging();
+ method public final void setSignal(android.os.Bundle);
method public static java.lang.String stateToString(int);
}
@@ -27728,10 +27757,12 @@
public final class ConnectionRequest implements android.os.Parcelable {
ctor public ConnectionRequest(android.net.Uri, android.os.Bundle);
ctor public ConnectionRequest(java.lang.String, android.net.Uri, android.os.Bundle);
+ ctor public ConnectionRequest(android.telecomm.Subscription, java.lang.String, android.net.Uri, android.os.Bundle);
method public int describeContents();
method public java.lang.String getCallId();
method public android.os.Bundle getExtras();
method public android.net.Uri getHandle();
+ method public android.telecomm.Subscription getSubscription();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
}
@@ -27741,14 +27772,17 @@
method public final void abort(java.lang.String);
method public final void answer(java.lang.String);
method public final void call(android.telecomm.CallInfo);
+ method public void createRemoteOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.OutgoingCallResponse<android.telecomm.RemoteConnection>);
method public final void disconnect(java.lang.String);
method public java.util.Collection<android.telecomm.Connection> getAllConnections();
method public final void hold(java.lang.String);
+ method public void lookupRemoteSubscriptions(android.net.Uri, android.telecomm.SimpleResponse<android.net.Uri, java.util.List<android.telecomm.Subscription>>);
+ method public void maybeRespondToSubscriptionLookup();
method public final void onAudioStateChanged(java.lang.String, android.telecomm.CallAudioState);
method public void onConnectionAdded(android.telecomm.Connection);
method public void onConnectionRemoved(android.telecomm.Connection);
method public void onCreateConferenceConnection(java.lang.String, android.telecomm.Connection, android.telecomm.Response<java.lang.String, android.telecomm.Connection>);
- method public void onCreateConnections(android.telecomm.ConnectionRequest, android.telecomm.Response<android.telecomm.ConnectionRequest, android.telecomm.Connection>);
+ method public void onCreateConnections(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.OutgoingCallResponse<android.telecomm.Connection>);
method public void onCreateIncomingConnection(android.telecomm.ConnectionRequest, android.telecomm.Response<android.telecomm.ConnectionRequest, android.telecomm.Connection>);
method public final void onPostDialContinue(java.lang.String, boolean);
method public final void onPostDialWait(android.telecomm.Connection, java.lang.String);
@@ -27759,6 +27793,12 @@
method public final void unhold(java.lang.String);
}
+ public static abstract interface ConnectionService.OutgoingCallResponse {
+ method public abstract void onCancel(android.telecomm.ConnectionRequest);
+ method public abstract void onFailure(android.telecomm.ConnectionRequest, int, java.lang.String);
+ method public abstract void onSuccess(android.telecomm.ConnectionRequest, CONNECTION);
+ }
+
public class GatewayInfo implements android.os.Parcelable {
method public int describeContents();
method public android.net.Uri getGatewayHandle();
@@ -27813,11 +27853,46 @@
method protected abstract void updateCall(android.telecomm.InCallCall);
}
+ public final class RemoteConnection {
+ method public void addListener(android.telecomm.RemoteConnection.Listener);
+ method public void answer();
+ method public void disconnect();
+ method public int getDisconnectCause();
+ method public java.lang.String getDisconnectMessage();
+ method public void hold();
+ method public void playDtmf(char);
+ method public void postDialContinue(boolean);
+ method public void reject();
+ method public void removeListener(android.telecomm.RemoteConnection.Listener);
+ method public void stopDtmf();
+ method public void unhold();
+ }
+
+ public static abstract interface RemoteConnection.Listener {
+ method public abstract void onAudioStateChanged(android.telecomm.RemoteConnection, android.telecomm.CallAudioState);
+ method public abstract void onDestroyed(android.telecomm.RemoteConnection);
+ method public abstract void onDisconnected(android.telecomm.RemoteConnection, int, java.lang.String);
+ method public abstract void onPostDialWait(android.telecomm.RemoteConnection, java.lang.String);
+ method public abstract void onRequestingRingback(android.telecomm.RemoteConnection, boolean);
+ method public abstract void onStateChanged(android.telecomm.RemoteConnection, int);
+ }
+
+ public class RemoteConnectionService implements android.os.IBinder.DeathRecipient {
+ method public void binderDied();
+ method public void createOutgoingConnection(android.telecomm.ConnectionRequest, android.telecomm.ConnectionService.OutgoingCallResponse<android.telecomm.RemoteConnection>);
+ method public java.util.List<android.telecomm.Subscription> lookupSubscriptions(android.net.Uri);
+ }
+
public abstract interface Response {
method public abstract void onError(IN, int, java.lang.String);
method public abstract void onResult(IN, OUT...);
}
+ public abstract interface SimpleResponse {
+ method public abstract void onError(IN);
+ method public abstract void onResult(IN, OUT);
+ }
+
public class Subscription implements android.os.Parcelable {
ctor public Subscription(android.content.ComponentName, java.lang.String, android.net.Uri, int, int, int, boolean, boolean);
method public int describeContents();
@@ -30633,6 +30708,9 @@
method public android.animation.Animator onAppear(android.view.ViewGroup, android.view.View, android.transition.TransitionValues, android.transition.TransitionValues);
method public android.animation.Animator onDisappear(android.view.ViewGroup, android.transition.TransitionValues, int, android.transition.TransitionValues, int);
method public android.animation.Animator onDisappear(android.view.ViewGroup, android.view.View, android.transition.TransitionValues, android.transition.TransitionValues);
+ method public void setMode(int);
+ field public static final int IN = 1; // 0x1
+ field public static final int OUT = 2; // 0x2
}
public abstract class VisibilityPropagation extends android.transition.TransitionPropagation {
@@ -34786,7 +34864,7 @@
ctor public CursorAnchorInfo(android.os.Parcel);
method public int describeContents();
method public android.graphics.RectF getCharacterRect(int);
- method public java.lang.String getComposingText();
+ method public java.lang.CharSequence getComposingText();
method public int getComposingTextStart();
method public float getInsertionMarkerBaseline();
method public float getInsertionMarkerBottom();
@@ -35213,6 +35291,7 @@
public class CookieManager {
method public synchronized boolean acceptCookie();
+ method public synchronized boolean acceptThirdPartyCookies(android.webkit.WebView);
method public static boolean allowFileSchemeCookies();
method public java.lang.String getCookie(java.lang.String);
method public static synchronized android.webkit.CookieManager getInstance();
@@ -35224,6 +35303,7 @@
method public void removeSessionCookies(android.webkit.ValueCallback<java.lang.Boolean>);
method public synchronized void setAcceptCookie(boolean);
method public static void setAcceptFileSchemeCookies(boolean);
+ method public synchronized void setAcceptThirdPartyCookies(android.webkit.WebView, boolean);
method public void setCookie(java.lang.String, java.lang.String);
method public void setCookie(java.lang.String, java.lang.String, android.webkit.ValueCallback<java.lang.Boolean>);
}
@@ -38177,6 +38257,7 @@
method public android.graphics.drawable.Drawable getLogo();
method public java.lang.CharSequence getLogoDescription();
method public android.view.Menu getMenu();
+ method public java.lang.CharSequence getNavigationContentDescription();
method public android.graphics.drawable.Drawable getNavigationIcon();
method public java.lang.CharSequence getSubtitle();
method public java.lang.CharSequence getTitle();
@@ -38353,6 +38434,22 @@
}
+package com.android.internal.telecomm {
+
+ public abstract interface RemoteServiceCallback implements android.os.IInterface {
+ method public abstract void onError() throws android.os.RemoteException;
+ method public abstract void onResult(java.util.List<android.content.ComponentName>, java.util.List<android.os.IBinder>) throws android.os.RemoteException;
+ }
+
+ public static abstract class RemoteServiceCallback.Stub extends android.os.Binder implements com.android.internal.telecomm.RemoteServiceCallback {
+ ctor public RemoteServiceCallback.Stub();
+ method public android.os.IBinder asBinder();
+ method public static com.android.internal.telecomm.RemoteServiceCallback asInterface(android.os.IBinder);
+ method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException;
+ }
+
+}
+
package com.android.internal.util {
public abstract interface Predicate {
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index d1bea2e..d683851 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -175,10 +175,13 @@
System.out.println("Performing full transport backup");
String pkg;
+ ArrayList<String> allPkgs = new ArrayList<String>();
while ((pkg = nextArg()) != null) {
- System.out.println(" Package " + pkg + " ...");
+ allPkgs.add(pkg);
+ }
+ if (allPkgs.size() > 0) {
try {
- mBmgr.fullTransportBackup(new String[] { pkg });
+ mBmgr.fullTransportBackup(allPkgs.toArray(new String[allPkgs.size()]));
} catch (RemoteException e) {
System.err.println(e.toString());
System.err.println(BMGR_NOT_RUNNING_ERR);
@@ -474,6 +477,7 @@
System.err.println(" bmgr restore PACKAGE");
System.err.println(" bmgr run");
System.err.println(" bmgr wipe TRANSPORT PACKAGE");
+ System.err.println(" bmgr fullbackup PACKAGE...");
System.err.println("");
System.err.println("The 'backup' command schedules a backup pass for the named package.");
System.err.println("Note that the backup pass will effectively be a no-op if the package");
@@ -489,11 +493,11 @@
System.err.println("");
System.err.println("The 'list transports' command reports the names of the backup transports");
System.err.println("currently available on the device. These names can be passed as arguments");
- System.err.println("to the 'transport' and 'wipe' commands. The currently selected transport");
+ System.err.println("to the 'transport' and 'wipe' commands. The currently active transport");
System.err.println("is indicated with a '*' character.");
System.err.println("");
System.err.println("The 'list sets' command reports the token and name of each restore set");
- System.err.println("available to the device via the current transport.");
+ System.err.println("available to the device via the currently active transport.");
System.err.println("");
System.err.println("The 'transport' command designates the named transport as the currently");
System.err.println("active one. This setting is persistent across reboots.");
@@ -521,5 +525,8 @@
System.err.println("erased from the given transport's storage. The next backup operation");
System.err.println("that the given application performs will rewrite its entire data set.");
System.err.println("Transport names to use here are those reported by 'list transports'.");
+ System.err.println("");
+ System.err.println("The 'fullbackup' command induces a full-data stream backup for one or more");
+ System.err.println("packages. The data is sent via the currently active transport.");
}
}
diff --git a/core/java/android/animation/StateListAnimator.java b/core/java/android/animation/StateListAnimator.java
index bc4843d..810f050 100644
--- a/core/java/android/animation/StateListAnimator.java
+++ b/core/java/android/animation/StateListAnimator.java
@@ -141,7 +141,7 @@
return;
}
if (mLastMatch != null) {
- cancel(mLastMatch);
+ cancel();
}
mLastMatch = match;
if (match != null) {
@@ -151,13 +151,15 @@
private void start(Tuple match) {
match.mAnimator.setTarget(getTarget());
- mRunningAnimator = match.mAnimator;
- match.mAnimator.start();
+ mRunningAnimator = match.mAnimator.clone();
+ mRunningAnimator.start();
}
- private void cancel(Tuple lastMatch) {
- lastMatch.mAnimator.cancel();
- lastMatch.mAnimator.setTarget(null);
+ private void cancel() {
+ if (mRunningAnimator != null) {
+ mRunningAnimator.cancel();
+ mRunningAnimator = null;
+ }
}
/**
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d0d0289..228d82e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -780,7 +780,7 @@
private Thread mUiThread;
final Handler mHandler = new Handler();
- private ActivityTransitionState mActivityTransitionState = new ActivityTransitionState();
+ ActivityTransitionState mActivityTransitionState = new ActivityTransitionState();
SharedElementListener mEnterTransitionListener = SharedElementListener.NULL_LISTENER;
SharedElementListener mExitTransitionListener = SharedElementListener.NULL_LISTENER;
@@ -3666,11 +3666,7 @@
* @see #startActivity
*/
public void startActivityForResult(Intent intent, int requestCode) {
- Bundle options = null;
- if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
- options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle();
- }
- startActivityForResult(intent, requestCode, options);
+ startActivityForResult(intent, requestCode, null);
}
/**
@@ -4221,11 +4217,7 @@
*/
public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent,
int requestCode) {
- Bundle options = null;
- if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
- options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle();
- }
- startActivityFromFragment(fragment, intent, requestCode, options);
+ startActivityFromFragment(fragment, intent, requestCode, null);
}
/**
@@ -5139,78 +5131,7 @@
return onCreateView(name, context, attrs);
}
- String fname = attrs.getAttributeValue(null, "class");
- TypedArray a =
- context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment);
- if (fname == null) {
- fname = a.getString(com.android.internal.R.styleable.Fragment_name);
- }
- int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, View.NO_ID);
- String tag = a.getString(com.android.internal.R.styleable.Fragment_tag);
- a.recycle();
-
- int containerId = parent != null ? parent.getId() : 0;
- if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
- throw new IllegalArgumentException(attrs.getPositionDescription()
- + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname);
- }
-
- // If we restored from a previous state, we may already have
- // instantiated this fragment from the state and should use
- // that instance instead of making a new one.
- Fragment fragment = id != View.NO_ID ? mFragments.findFragmentById(id) : null;
- if (fragment == null && tag != null) {
- fragment = mFragments.findFragmentByTag(tag);
- }
- if (fragment == null && containerId != View.NO_ID) {
- fragment = mFragments.findFragmentById(containerId);
- }
-
- if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x"
- + Integer.toHexString(id) + " fname=" + fname
- + " existing=" + fragment);
- if (fragment == null) {
- fragment = Fragment.instantiate(this, fname);
- fragment.mFromLayout = true;
- fragment.mFragmentId = id != 0 ? id : containerId;
- fragment.mContainerId = containerId;
- fragment.mTag = tag;
- fragment.mInLayout = true;
- fragment.mFragmentManager = mFragments;
- fragment.onInflate(this, attrs, fragment.mSavedFragmentState);
- mFragments.addFragment(fragment, true);
-
- } else if (fragment.mInLayout) {
- // A fragment already exists and it is not one we restored from
- // previous state.
- throw new IllegalArgumentException(attrs.getPositionDescription()
- + ": Duplicate id 0x" + Integer.toHexString(id)
- + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId)
- + " with another fragment for " + fname);
- } else {
- // This fragment was retained from a previous instance; get it
- // going now.
- fragment.mInLayout = true;
- // If this fragment is newly instantiated (either right now, or
- // from last saved state), then give it the attributes to
- // initialize itself.
- if (!fragment.mRetaining) {
- fragment.onInflate(this, attrs, fragment.mSavedFragmentState);
- }
- mFragments.moveToState(fragment);
- }
-
- if (fragment.mView == null) {
- throw new IllegalStateException("Fragment " + fname
- + " did not create a view.");
- }
- if (id != 0) {
- fragment.mView.setId(id);
- }
- if (fragment.mView.getTag() == null) {
- fragment.mView.setTag(tag);
- }
- return fragment.mView;
+ return mFragments.onCreateView(parent, name, context, attrs);
}
/**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 788ac56..87140a3 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -538,6 +538,30 @@
}
/**
+ * Sets the label for this task description.
+ * @hide
+ */
+ public void setLabel(String label) {
+ mLabel = label;
+ }
+
+ /**
+ * Sets the primary color for this task description.
+ * @hide
+ */
+ public void setPrimaryColor(int primaryColor) {
+ mColorPrimary = primaryColor;
+ }
+
+ /**
+ * Sets the icon for this task description.
+ * @hide
+ */
+ public void setIcon(Bitmap icon) {
+ mIcon = icon;
+ }
+
+ /**
* @return The label and description of the current state of this task.
*/
public String getLabel() {
@@ -652,6 +676,12 @@
public int userId;
/**
+ * The last time this task was active.
+ * @hide
+ */
+ public long lastActiveTime;
+
+ /**
* The recent activity values for the highest activity in the stack to have set the values.
* {@link Activity#setTaskDescription(android.app.ActivityManager.TaskDescription)}.
*
@@ -688,6 +718,7 @@
}
dest.writeInt(stackId);
dest.writeInt(userId);
+ dest.writeLong(lastActiveTime);
}
public void readFromParcel(Parcel source) {
@@ -700,6 +731,7 @@
TaskDescription.CREATOR.createFromParcel(source) : null;
stackId = source.readInt();
userId = source.readInt();
+ lastActiveTime = source.readLong();
}
public static final Creator<RecentTaskInfo> CREATOR
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 0ecead9..6b5549d 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -108,9 +108,9 @@
private static final String KEY_TRANSITION_IS_RETURNING = "android:transitionIsReturning";
private static final String KEY_TRANSITION_SHARED_ELEMENTS = "android:sharedElementNames";
- private static final String KEY_LOCAL_SHARED_ELEMENTS = "android:localSharedElementNames";
private static final String KEY_RESULT_DATA = "android:resultData";
private static final String KEY_RESULT_CODE = "android:resultCode";
+ private static final String KEY_EXIT_COORDINATOR_INDEX = "android:exitCoordinatorIndex";
/** @hide */
public static final int ANIM_NONE = 0;
@@ -138,9 +138,9 @@
private ResultReceiver mTransitionReceiver;
private boolean mIsReturning;
private ArrayList<String> mSharedElementNames;
- private ArrayList<String> mLocalSharedElementNames;
private Intent mResultData;
private int mResultCode;
+ private int mExitCoordinatorIndex;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -387,7 +387,7 @@
opts.mAnimationType = ANIM_SCENE_TRANSITION;
ArrayList<String> names = new ArrayList<String>();
- ArrayList<String> mappedNames = new ArrayList<String>();
+ ArrayList<View> views = new ArrayList<View>();
if (sharedElements != null) {
for (int i = 0; i < sharedElements.length; i++) {
@@ -396,23 +396,22 @@
if (sharedElementName == null) {
throw new IllegalArgumentException("Shared element name must not be null");
}
- String name = sharedElement.first.getTransitionName();
- if (name == null) {
- throw new IllegalArgumentException("Shared elements must have non-null " +
- "transitionNames");
- }
-
names.add(sharedElementName);
- mappedNames.add(name);
+ View view = sharedElement.first;
+ if (view == null) {
+ throw new IllegalArgumentException("Shared element must not be null");
+ }
+ views.add(sharedElement.first);
}
}
ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, names, names,
- mappedNames, false);
+ views, false);
opts.mTransitionReceiver = exit;
opts.mSharedElementNames = names;
- opts.mLocalSharedElementNames = mappedNames;
opts.mIsReturning = false;
+ opts.mExitCoordinatorIndex =
+ activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
return opts;
}
@@ -427,6 +426,8 @@
opts.mIsReturning = true;
opts.mResultCode = resultCode;
opts.mResultData = resultData;
+ opts.mExitCoordinatorIndex =
+ activity.mActivityTransitionState.addExitTransitionCoordinator(exitCoordinator);
return opts;
}
@@ -465,9 +466,9 @@
mTransitionReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER);
mIsReturning = opts.getBoolean(KEY_TRANSITION_IS_RETURNING, false);
mSharedElementNames = opts.getStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS);
- mLocalSharedElementNames = opts.getStringArrayList(KEY_LOCAL_SHARED_ELEMENTS);
mResultData = opts.getParcelable(KEY_RESULT_DATA);
mResultCode = opts.getInt(KEY_RESULT_CODE);
+ mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX);
break;
}
}
@@ -523,18 +524,7 @@
}
/** @hide */
- public void dispatchActivityStopped() {
- if (mTransitionReceiver != null) {
- mTransitionReceiver.send(ActivityTransitionCoordinator.MSG_ACTIVITY_STOPPED, null);
- }
- }
-
- /** @hide */
- public void dispatchStartExit() {
- if (mTransitionReceiver != null) {
- mTransitionReceiver.send(ActivityTransitionCoordinator.MSG_START_EXIT_TRANSITION, null);
- }
- }
+ public int getExitCoordinatorKey() { return mExitCoordinatorIndex; }
/** @hide */
public void abort() {
@@ -547,11 +537,6 @@
}
/** @hide */
- public void setReturning() {
- mIsReturning = true;
- }
-
- /** @hide */
public boolean isReturning() {
return mIsReturning;
}
@@ -562,9 +547,6 @@
}
/** @hide */
- public ArrayList<String> getLocalSharedElementNames() { return mLocalSharedElementNames; }
-
- /** @hide */
public ResultReceiver getResultReceiver() { return mTransitionReceiver; }
/** @hide */
@@ -591,10 +573,10 @@
}
mTransitionReceiver = null;
mSharedElementNames = null;
- mLocalSharedElementNames = null;
mIsReturning = false;
mResultData = null;
mResultCode = 0;
+ mExitCoordinatorIndex = 0;
switch (otherOptions.mAnimationType) {
case ANIM_CUSTOM:
mAnimationType = otherOptions.mAnimationType;
@@ -641,12 +623,12 @@
mAnimationType = otherOptions.mAnimationType;
mTransitionReceiver = otherOptions.mTransitionReceiver;
mSharedElementNames = otherOptions.mSharedElementNames;
- mLocalSharedElementNames = otherOptions.mLocalSharedElementNames;
mIsReturning = otherOptions.mIsReturning;
mThumbnail = null;
mAnimationStartedListener = null;
mResultData = otherOptions.mResultData;
mResultCode = otherOptions.mResultCode;
+ mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
break;
}
}
@@ -695,9 +677,9 @@
}
b.putBoolean(KEY_TRANSITION_IS_RETURNING, mIsReturning);
b.putStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS, mSharedElementNames);
- b.putStringArrayList(KEY_LOCAL_SHARED_ELEMENTS, mLocalSharedElementNames);
b.putParcelable(KEY_RESULT_DATA, mResultData);
b.putInt(KEY_RESULT_CODE, mResultCode);
+ b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
break;
}
return b;
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index c351cd5..79dc6a8 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -151,12 +151,6 @@
public static final int MSG_HIDE_SHARED_ELEMENTS = 101;
/**
- * Sent by the exiting Activity in ActivityOptions#dispatchActivityStopped
- * to leave the Activity in a good state after it has been hidden.
- */
- public static final int MSG_ACTIVITY_STOPPED = 102;
-
- /**
* Sent by the exiting coordinator (either EnterTransitionCoordinator
* or ExitTransitionCoordinator) after the shared elements have
* become stationary (shared element transition completes). This tells
@@ -197,7 +191,7 @@
*/
public static final int MSG_SEND_SHARED_ELEMENT_DESTINATION = 108;
- final private Window mWindow;
+ private Window mWindow;
final protected ArrayList<String> mAllSharedElementNames;
final protected ArrayList<View> mSharedElements = new ArrayList<View>();
final protected ArrayList<String> mSharedElementNames = new ArrayList<String>();
@@ -209,15 +203,6 @@
private Runnable mPendingTransition;
private boolean mIsStartingTransition;
-
- public ActivityTransitionCoordinator(Window window,
- ArrayList<String> allSharedElementNames,
- ArrayList<String> accepted, ArrayList<String> localNames,
- SharedElementListener listener, boolean isReturning) {
- this(window, allSharedElementNames, listener, isReturning);
- viewsReady(accepted, localNames);
- }
-
public ActivityTransitionCoordinator(Window window,
ArrayList<String> allSharedElementNames,
SharedElementListener listener, boolean isReturning) {
@@ -228,22 +213,25 @@
mIsReturning = isReturning;
}
- protected void viewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
- setSharedElements(accepted, localNames);
+ protected void viewsReady(ArrayMap<String, View> sharedElements) {
+ setSharedElements(sharedElements);
if (getViewsTransition() != null) {
getDecor().captureTransitioningViews(mTransitioningViews);
mTransitioningViews.removeAll(mSharedElements);
- Rect r = new Rect();
- for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
- View view = mTransitioningViews.get(i);
- if (!view.getGlobalVisibleRect(r)) {
- mTransitioningViews.remove(i);
- }
- }
}
setEpicenter();
}
+ protected void stripOffscreenViews() {
+ Rect r = new Rect();
+ for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
+ View view = mTransitioningViews.get(i);
+ if (!view.getGlobalVisibleRect(r)) {
+ mTransitioningViews.remove(i);
+ }
+ }
+ }
+
protected Window getWindow() {
return mWindow;
}
@@ -286,6 +274,10 @@
return names;
}
+ public ArrayList<View> getMappedViews() {
+ return mSharedElements;
+ }
+
public ArrayList<String> getAllSharedElementNames() { return mAllSharedElementNames; }
public static void setViewVisibility(Collection<View> views, int visibility) {
@@ -335,36 +327,31 @@
}
}
- private void setSharedElements(ArrayList<String> accepted, ArrayList<String> localNames) {
+ protected ArrayMap<String, View> mapSharedElements(ArrayList<String> accepted,
+ ArrayList<View> localViews) {
+ ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
if (!mAllSharedElementNames.isEmpty()) {
- ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
- getDecor().findNamedViews(sharedElements);
if (accepted != null) {
- for (int i = 0; i < localNames.size(); i++) {
- String localName = localNames.get(i);
- String acceptedName = accepted.get(i);
- if (!localName.equals(acceptedName)) {
- View view = sharedElements.remove(localName);
- if (view != null) {
- sharedElements.put(acceptedName, view);
- }
- }
+ for (int i = 0; i < accepted.size(); i++) {
+ sharedElements.put(accepted.get(i), localViews.get(i));
}
+ } else {
+ getDecor().findNamedViews(sharedElements);
}
- sharedElements.retainAll(mAllSharedElementNames);
- mListener.remapSharedElements(mAllSharedElementNames, sharedElements);
- sharedElements.retainAll(mAllSharedElementNames);
- for (int i = 0; i < mAllSharedElementNames.size(); i++) {
- String name = mAllSharedElementNames.get(i);
- View sharedElement = sharedElements.get(name);
- if (sharedElement != null) {
- if (sharedElement.getTransitionName() == null) {
- throw new IllegalArgumentException("Shared elements must have " +
- "non-null transitionNames");
- }
- mSharedElementNames.add(name);
- mSharedElements.add(sharedElement);
- }
+ }
+ return sharedElements;
+ }
+
+ private void setSharedElements(ArrayMap<String, View> sharedElements) {
+ sharedElements.retainAll(mAllSharedElementNames);
+ mListener.remapSharedElements(mAllSharedElementNames, sharedElements);
+ sharedElements.retainAll(mAllSharedElementNames);
+ for (int i = 0; i < mAllSharedElementNames.size(); i++) {
+ String name = mAllSharedElementNames.get(i);
+ View sharedElement = sharedElements.get(name);
+ if (sharedElement != null) {
+ mSharedElementNames.add(name);
+ mSharedElements.add(sharedElement);
}
}
}
@@ -523,6 +510,17 @@
return bundle;
}
+ protected void clearState() {
+ // Clear the state so that we can't hold any references accidentally and leak memory.
+ mWindow = null;
+ mAllSharedElementNames.clear();
+ mSharedElements.clear();
+ mSharedElementNames.clear();
+ mTransitioningViews.clear();
+ mResultReceiver = null;
+ mPendingTransition = null;
+ }
+
protected long getFadeDuration() {
return getWindow().getTransitionBackgroundFadeDuration();
}
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index d94dadd..0304246 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -18,9 +18,11 @@
import android.os.Bundle;
import android.os.ResultReceiver;
import android.util.ArrayMap;
+import android.util.SparseArray;
import android.view.View;
import android.view.Window;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
@@ -31,10 +33,6 @@
private static final String ENTERING_SHARED_ELEMENTS = "android:enteringSharedElements";
- private static final String ENTERING_MAPPED_FROM = "android:enteringMappedFrom";
-
- private static final String ENTERING_MAPPED_TO = "android:enteringMappedTo";
-
private static final String EXITING_MAPPED_FROM = "android:exitingMappedFrom";
private static final String EXITING_MAPPED_TO = "android:exitingMappedTo";
@@ -46,16 +44,6 @@
private ArrayList<String> mEnteringNames;
/**
- * The shared elements that this Activity as accepted and mapped to local Views.
- */
- private ArrayList<String> mEnteringFrom;
-
- /**
- * The names of local Views that are mapped to those elements in mEnteringFrom.
- */
- private ArrayList<String> mEnteringTo;
-
- /**
* The names of shared elements that were shared to the called Activity.
*/
private ArrayList<String> mExitingFrom;
@@ -66,10 +54,15 @@
private ArrayList<String> mExitingTo;
/**
- * The ActivityOptions used to call an Activity. Used to make the elements restore
+ * The local Views that were shared out, mapped to those elements in mExitingFrom.
+ */
+ private ArrayList<View> mExitingToView;
+
+ /**
+ * The ExitTransitionCoordinator used to start an Activity. Used to make the elements restore
* Visibility of exited Views.
*/
- private ActivityOptions mCalledActivityOptions;
+ private ExitTransitionCoordinator mCalledExitCoordinator;
/**
* We must be able to cancel entering transitions to stop changing the Window to
@@ -92,15 +85,42 @@
*/
private boolean mIsEnterPostponed;
+ /**
+ * Potential exit transition coordinators.
+ */
+ private SparseArray<WeakReference<ExitTransitionCoordinator>> mExitTransitionCoordinators;
+
+ /**
+ * Next key for mExitTransitionCoordinator.
+ */
+ private int mExitTransitionCoordinatorsKey = 1;
+
public ActivityTransitionState() {
}
+ public int addExitTransitionCoordinator(ExitTransitionCoordinator exitTransitionCoordinator) {
+ if (mExitTransitionCoordinators == null) {
+ mExitTransitionCoordinators =
+ new SparseArray<WeakReference<ExitTransitionCoordinator>>();
+ }
+ WeakReference<ExitTransitionCoordinator> ref = new WeakReference(exitTransitionCoordinator);
+ // clean up old references:
+ for (int i = mExitTransitionCoordinators.size() - 1; i >= 0; i--) {
+ WeakReference<ExitTransitionCoordinator> oldRef
+ = mExitTransitionCoordinators.valueAt(i);
+ if (oldRef.get() == null) {
+ mExitTransitionCoordinators.removeAt(i);
+ }
+ }
+ int newKey = mExitTransitionCoordinatorsKey++;
+ mExitTransitionCoordinators.append(newKey, ref);
+ return newKey;
+ }
+
public void readState(Bundle bundle) {
if (bundle != null) {
if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isReturning()) {
mEnteringNames = bundle.getStringArrayList(ENTERING_SHARED_ELEMENTS);
- mEnteringFrom = bundle.getStringArrayList(ENTERING_MAPPED_FROM);
- mEnteringTo = bundle.getStringArrayList(ENTERING_MAPPED_TO);
}
if (mEnterTransitionCoordinator == null) {
mExitingFrom = bundle.getStringArrayList(EXITING_MAPPED_FROM);
@@ -112,8 +132,6 @@
public void saveState(Bundle bundle) {
if (mEnteringNames != null) {
bundle.putStringArrayList(ENTERING_SHARED_ELEMENTS, mEnteringNames);
- bundle.putStringArrayList(ENTERING_MAPPED_FROM, mEnteringFrom);
- bundle.putStringArrayList(ENTERING_MAPPED_TO, mEnteringTo);
}
if (mExitingFrom != null) {
bundle.putStringArrayList(EXITING_MAPPED_FROM, mExitingFrom);
@@ -169,16 +187,19 @@
private void startEnter() {
if (mEnterActivityOptions.isReturning()) {
- mEnterTransitionCoordinator.viewsReady(mExitingFrom, mExitingTo);
+ if (mExitingToView != null) {
+ mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingToView);
+ } else {
+ mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo);
+ }
} else {
- mEnterTransitionCoordinator.viewsReady(null, null);
+ mEnterTransitionCoordinator.namedViewsReady(null, null);
mEnteringNames = mEnterTransitionCoordinator.getAllSharedElementNames();
- mEnteringFrom = mEnterTransitionCoordinator.getAcceptedNames();
- mEnteringTo = mEnterTransitionCoordinator.getMappedNames();
}
mExitingFrom = null;
mExitingTo = null;
+ mExitingToView = null;
mEnterActivityOptions = null;
}
@@ -194,10 +215,21 @@
restoreExitedViews();
}
+ public void clear() {
+ mEnteringNames = null;
+ mExitingFrom = null;
+ mExitingTo = null;
+ mExitingToView = null;
+ mCalledExitCoordinator = null;
+ mEnterTransitionCoordinator = null;
+ mEnterActivityOptions = null;
+ mExitTransitionCoordinators = null;
+ }
+
private void restoreExitedViews() {
- if (mCalledActivityOptions != null) {
- mCalledActivityOptions.dispatchActivityStopped();
- mCalledActivityOptions = null;
+ if (mCalledExitCoordinator != null) {
+ mCalledExitCoordinator.resetViews();
+ mCalledExitCoordinator = null;
}
}
@@ -215,8 +247,7 @@
activity.getWindow().getDecorView().findNamedViews(sharedElements);
ExitTransitionCoordinator exitCoordinator =
- new ExitTransitionCoordinator(activity, mEnteringNames, mEnteringFrom,
- mEnteringTo, true);
+ new ExitTransitionCoordinator(activity, mEnteringNames, null, null, true);
exitCoordinator.startExit(activity.mResultCode, activity.mResultData);
}
return true;
@@ -227,11 +258,20 @@
if (!activity.getWindow().hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
return;
}
- mCalledActivityOptions = new ActivityOptions(options);
- if (mCalledActivityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
- mExitingFrom = mCalledActivityOptions.getSharedElementNames();
- mExitingTo = mCalledActivityOptions.getLocalSharedElementNames();
- mCalledActivityOptions.dispatchStartExit();
+ ActivityOptions activityOptions = new ActivityOptions(options);
+ if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+ int key = activityOptions.getExitCoordinatorKey();
+ int index = mExitTransitionCoordinators.indexOfKey(key);
+ if (index >= 0) {
+ mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get();
+ mExitTransitionCoordinators.removeAt(index);
+ if (mCalledExitCoordinator != null) {
+ mExitingFrom = mCalledExitCoordinator.getAcceptedNames();
+ mExitingTo = mCalledExitCoordinator.getMappedNames();
+ mExitingToView = mCalledExitCoordinator.getMappedViews();
+ mCalledExitCoordinator.startExit();
+ }
+ }
}
}
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 13b922c..cb48e58 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -621,10 +621,15 @@
}
@Override
- public ProviderInfo resolveContentProvider(String name,
- int flags) {
+ public ProviderInfo resolveContentProvider(String name, int flags) {
+ return resolveContentProviderAsUser(name, flags, mContext.getUserId());
+ }
+
+ /** @hide **/
+ @Override
+ public ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId) {
try {
- return mPM.resolveContentProvider(name, flags, mContext.getUserId());
+ return mPM.resolveContentProvider(name, flags, userId);
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 1d7a0ec..6c71bb1 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -79,12 +79,24 @@
});
}
- public void viewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
+ public void viewInstancesReady(ArrayList<String> accepted, ArrayList<View> localViews) {
if (mIsReadyForTransition) {
return;
}
- super.viewsReady(accepted, localNames);
+ viewsReady(mapSharedElements(accepted, localViews));
+ }
+ public void namedViewsReady(ArrayList<String> accepted, ArrayList<String> localNames) {
+ if (mIsReadyForTransition) {
+ return;
+ }
+
+ viewsReady(mapNamedElements(accepted, localNames));
+ }
+
+ @Override
+ protected void viewsReady(ArrayMap<String, View> sharedElements) {
+ super.viewsReady(sharedElements);
mIsReadyForTransition = true;
if (mIsReturning) {
mHandler = new Handler() {
@@ -105,6 +117,25 @@
}
}
+ private ArrayMap<String, View> mapNamedElements(ArrayList<String> accepted,
+ ArrayList<String> localNames) {
+ ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
+ getDecor().findNamedViews(sharedElements);
+ if (accepted != null) {
+ for (int i = 0; i < localNames.size(); i++) {
+ String localName = localNames.get(i);
+ String acceptedName = accepted.get(i);
+ if (localName != null && !localName.equals(acceptedName)) {
+ View view = sharedElements.remove(localName);
+ if (view != null) {
+ sharedElements.put(acceptedName, view);
+ }
+ }
+ }
+ }
+ return sharedElements;
+ }
+
private void sendSharedElementDestination() {
ViewGroup decor = getDecor();
boolean allReady = !decor.isLayoutRequested();
@@ -255,6 +286,13 @@
mResultReceiver = null; // all done sending messages.
}
+ @Override
+ protected void stripOffscreenViews() {
+ setViewVisibility(mTransitioningViews, View.VISIBLE);
+ super.stripOffscreenViews();
+ setViewVisibility(mTransitioningViews, View.INVISIBLE);
+ }
+
private void onTakeSharedElements() {
if (!mIsReadyForTransition || mSharedElementsBundle == null) {
return;
@@ -294,7 +332,10 @@
Transition viewsTransition = null;
if (startEnterTransition && !mTransitioningViews.isEmpty()) {
viewsTransition = configureTransition(getViewsTransition());
- viewsTransition = addTargets(viewsTransition, mTransitioningViews);
+ if (viewsTransition != null) {
+ stripOffscreenViews();
+ viewsTransition = addTargets(viewsTransition, mTransitioningViews);
+ }
}
Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 9f3dbdc..e38f527 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -27,6 +27,7 @@
import android.os.Message;
import android.transition.Transition;
import android.transition.TransitionManager;
+import android.util.ArrayMap;
import android.view.View;
import android.view.ViewGroupOverlay;
import android.view.ViewTreeObserver;
@@ -67,9 +68,11 @@
private ArrayList<View> mSharedElementSnapshots;
public ExitTransitionCoordinator(Activity activity, ArrayList<String> names,
- ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) {
- super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning),
+ ArrayList<String> accepted, ArrayList<View> mapped, boolean isReturning) {
+ super(activity.getWindow(), names, getListener(activity, isReturning),
isReturning);
+ viewsReady(mapSharedElements(accepted, mapped));
+ stripOffscreenViews();
mIsBackgroundReady = !isReturning;
mActivity = activity;
}
@@ -101,11 +104,6 @@
case MSG_START_EXIT_TRANSITION:
startExit();
break;
- case MSG_ACTIVITY_STOPPED:
- setViewVisibility(mTransitioningViews, View.VISIBLE);
- setViewVisibility(mSharedElements, View.VISIBLE);
- mIsHidden = true;
- break;
case MSG_SHARED_ELEMENT_DESTINATION:
mExitSharedElementBundle = resultData;
sharedElementExitBack();
@@ -113,6 +111,12 @@
}
}
+ public void resetViews() {
+ setViewVisibility(mTransitioningViews, View.VISIBLE);
+ setViewVisibility(mSharedElements, View.VISIBLE);
+ mIsHidden = true;
+ }
+
private void sharedElementExitBack() {
if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) {
startTransition(new Runnable() {
@@ -131,6 +135,7 @@
transition.addListener(new Transition.TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
+ transition.removeListener(this);
setViewVisibility(mSharedElements, View.INVISIBLE);
ViewGroupOverlay overlay = getDecor().getOverlay();
if (mSharedElementSnapshots != null) {
@@ -193,8 +198,7 @@
@Override
public void handleMessage(Message msg) {
mIsCanceled = true;
- mActivity.finish();
- mActivity = null;
+ finish();
}
};
mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
@@ -223,13 +227,7 @@
}
private void startExitTransition() {
- Transition sharedElementTransition = mSharedElements.isEmpty()
- ? null : getSharedElementTransition();
- if (sharedElementTransition == null) {
- sharedElementTransitionComplete();
- }
- Transition transition = mergeTransitions(sharedElementTransition,
- getExitTransition());
+ Transition transition = getExitTransition();
if (transition != null) {
TransitionManager.beginDelayedTransition(getDecor(), transition);
setViewVisibility(mTransitioningViews, View.INVISIBLE);
@@ -266,6 +264,7 @@
viewsTransition.addListener(new ContinueTransitionListener() {
@Override
public void onTransitionEnd(Transition transition) {
+ transition.removeListener(this);
exitTransitionComplete();
if (mIsHidden) {
setViewVisibility(mTransitioningViews, View.VISIBLE);
@@ -292,6 +291,7 @@
sharedElementTransition.addListener(new ContinueTransitionListener() {
@Override
public void onTransitionEnd(Transition transition) {
+ transition.removeListener(this);
sharedElementTransitionComplete();
if (mIsHidden) {
setViewVisibility(mSharedElements, View.VISIBLE);
@@ -361,15 +361,31 @@
private void finishIfNecessary() {
if (mIsReturning && mExitNotified && mActivity != null && mSharedElementSnapshots == null) {
- mActivity.finish();
- mActivity.overridePendingTransition(0, 0);
- mActivity = null;
+ finish();
}
if (!mIsReturning && mExitNotified) {
mActivity = null; // don't need it anymore
}
}
+ private void finish() {
+ mActivity.mActivityTransitionState.clear();
+ // Clear the state so that we can't hold any references accidentally and leak memory.
+ mHandler.removeMessages(MSG_CANCEL);
+ mHandler = null;
+ mActivity.finish();
+ mActivity.overridePendingTransition(0, 0);
+ mActivity = null;
+ mSharedElementBundle = null;
+ if (mBackgroundAnimator != null) {
+ mBackgroundAnimator.cancel();
+ mBackgroundAnimator = null;
+ }
+ mExitSharedElementBundle = null;
+ mSharedElementSnapshots = null;
+ clearState();
+ }
+
@Override
protected Transition getViewsTransition() {
if (mIsReturning) {
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 6c0d379..2ff3d57 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -1104,7 +1105,15 @@
* inflation. Maybe this should become a public API. Note sure.
*/
public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
- return mActivity.getLayoutInflater();
+ // Newer platform versions use the child fragment manager's LayoutInflaterFactory.
+ if (mActivity.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) {
+ LayoutInflater result = mActivity.getLayoutInflater().cloneInContext(mActivity);
+ getChildFragmentManager(); // Init if needed; use raw implementation below.
+ result.setPrivateFactory(mChildFragmentManager.getLayoutInflaterFactory());
+ return result;
+ } else {
+ return mActivity.getLayoutInflater();
+ }
}
/**
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index b8f1962..a97fa650 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -19,6 +19,7 @@
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Bundle;
@@ -27,11 +28,13 @@
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.AttributeSet;
import android.util.DebugUtils;
import android.util.Log;
import android.util.LogWriter;
import android.util.SparseArray;
import android.util.SuperNotCalledException;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -397,7 +400,7 @@
/**
* Container for fragments associated with an activity.
*/
-final class FragmentManagerImpl extends FragmentManager {
+final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
static boolean DEBUG = false;
static final String TAG = "FragmentManager";
@@ -432,7 +435,7 @@
boolean mDestroyed;
String mNoTransactionsBecause;
boolean mHavePendingDeferredStart;
-
+
// Temporary vars for state save and restore.
Bundle mStateBundle = null;
SparseArray<Parcelable> mStateArray = null;
@@ -2052,4 +2055,100 @@
}
return animAttr;
}
+
+ @Override
+ public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+ if (!"fragment".equals(name)) {
+ return null;
+ }
+
+ String fname = attrs.getAttributeValue(null, "class");
+ TypedArray a =
+ context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment);
+ if (fname == null) {
+ fname = a.getString(com.android.internal.R.styleable.Fragment_name);
+ }
+ int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, View.NO_ID);
+ String tag = a.getString(com.android.internal.R.styleable.Fragment_tag);
+ a.recycle();
+
+ int containerId = parent != null ? parent.getId() : 0;
+ if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
+ throw new IllegalArgumentException(attrs.getPositionDescription()
+ + ": Must specify unique android:id, android:tag, or have a parent with"
+ + " an id for " + fname);
+ }
+
+ // If we restored from a previous state, we may already have
+ // instantiated this fragment from the state and should use
+ // that instance instead of making a new one.
+ Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null;
+ if (fragment == null && tag != null) {
+ fragment = findFragmentByTag(tag);
+ }
+ if (fragment == null && containerId != View.NO_ID) {
+ fragment = findFragmentById(containerId);
+ }
+
+ if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x"
+ + Integer.toHexString(id) + " fname=" + fname
+ + " existing=" + fragment);
+ if (fragment == null) {
+ fragment = Fragment.instantiate(context, fname);
+ fragment.mFromLayout = true;
+ fragment.mFragmentId = id != 0 ? id : containerId;
+ fragment.mContainerId = containerId;
+ fragment.mTag = tag;
+ fragment.mInLayout = true;
+ fragment.mFragmentManager = this;
+ fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState);
+ addFragment(fragment, true);
+ } else if (fragment.mInLayout) {
+ // A fragment already exists and it is not one we restored from
+ // previous state.
+ throw new IllegalArgumentException(attrs.getPositionDescription()
+ + ": Duplicate id 0x" + Integer.toHexString(id)
+ + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId)
+ + " with another fragment for " + fname);
+ } else {
+ // This fragment was retained from a previous instance; get it
+ // going now.
+ fragment.mInLayout = true;
+ // If this fragment is newly instantiated (either right now, or
+ // from last saved state), then give it the attributes to
+ // initialize itself.
+ if (!fragment.mRetaining) {
+ fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState);
+ }
+ }
+
+ // If we haven't finished entering the CREATED state ourselves yet,
+ // push the inflated child fragment along.
+ if (mCurState < Fragment.CREATED && fragment.mFromLayout) {
+ moveToState(fragment, Fragment.CREATED, 0, 0, false);
+ } else {
+ moveToState(fragment);
+ }
+
+ if (fragment.mView == null) {
+ throw new IllegalStateException("Fragment " + fname
+ + " did not create a view.");
+ }
+ if (id != 0) {
+ fragment.mView.setId(id);
+ }
+ if (fragment.mView.getTag() == null) {
+ fragment.mView.setTag(tag);
+ }
+ return fragment.mView;
+ }
+
+ @Override
+ public View onCreateView(String name, Context context, AttributeSet attrs) {
+ return null;
+ }
+
+ LayoutInflater.Factory2 getLayoutInflaterFactory() {
+ return this;
+ }
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index df6be8b..4351f9d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2349,7 +2349,7 @@
* <p>Any packages that shares uid with an allowed package will also be allowed
* to activate lock task.
*
- * This function can only be called by the device owner or the profile owner.
+ * This function can only be called by the device owner.
* @param packages The list of packages allowed to enter lock task mode
*
* @see Activity#startLockTask()
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index ad4ccbb..6fbf87d 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -26,6 +26,7 @@
interface ITrustManager {
void reportUnlockAttempt(boolean successful, int userId);
void reportEnabledTrustAgentsChanged(int userId);
+ void reportRequireCredentialEntry(int userId);
void registerTrustListener(in ITrustListener trustListener);
void unregisterTrustListener(in ITrustListener trustListener);
}
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index e31c624..6e90590 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -71,6 +71,21 @@
}
/**
+ * Reports that trust is disabled until credentials have been entered for user {@param userId}.
+ *
+ * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+ *
+ * @param userId either an explicit user id or {@link android.os.UserHandle#USER_ALL}
+ */
+ public void reportRequireCredentialEntry(int userId) {
+ try {
+ mService.reportRequireCredentialEntry(userId);
+ } catch (RemoteException e) {
+ onError(e);
+ }
+ }
+
+ /**
* Registers a listener for trust events.
*
* Requires the {@link android.Manifest.permission#TRUST_LISTENER} permission.
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index ba42f51b..2287246 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -2079,12 +2079,15 @@
public void onMultiAdvertiseCallback(int status) {
// no op
}
- /**
- * Callback reporting LE ATT MTU.
- * @hide
- */
+
+ @Override
public void onConfigureMTU(String address, int mtu, int status) {
// no op
}
+
+ @Override
+ public void onConnectionCongested(String address, boolean congested) {
+ // no op
+ }
}
}
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index c9df9c0..6c1a45e 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -85,6 +85,11 @@
/** A write operation exceeds the maximum length of the attribute */
public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 0xd;
+ /** A remote device connection is congested.
+ * @hide
+ */
+ public static final int GATT_CONNECTION_CONGESTED = 0x8f;
+
/** A GATT operation failed, errors other than the above */
public static final int GATT_FAILURE = 0x101;
@@ -267,7 +272,7 @@
if (service == null) return;
BluetoothGattCharacteristic characteristic = service.getCharacteristic(
- charUuid.getUuid());
+ charUuid.getUuid(), charInstId);
if (characteristic == null) return;
characteristic.addDescriptor(new BluetoothGattDescriptor(
@@ -607,6 +612,21 @@
Log.w(TAG, "Unhandled exception in callback", ex);
}
}
+
+ /**
+ * Callback indicating the remote device connection is congested.
+ * @hide
+ */
+ public void onConnectionCongested(String address, boolean congested) {
+ if (DBG) Log.d(TAG, "onConnectionCongested() - Device=" + address
+ + " congested=" + congested);
+ if (!address.equals(mDevice.getAddress())) return;
+ try {
+ mCallback.onConnectionCongested(BluetoothGatt.this, congested);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception in callback", ex);
+ }
+ }
};
/*package*/ BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device,
diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java
index 5180259..b5e60f2 100644
--- a/core/java/android/bluetooth/BluetoothGattCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattCallback.java
@@ -153,4 +153,19 @@
*/
public void onConfigureMTU(BluetoothGatt gatt, int mtu, int status) {
}
+
+ /**
+ * Callback indicating that a remote device connection congestestion status has changed.
+ *
+ * An application should refrain from sending additional data to a remote device when
+ * a callback is received with the congested flag set to true. Once the congestion status
+ * is cleared up, the application will receive an additional callback with the congested
+ * flag set to false.
+ *
+ * @param gatt The GATT client associated with the remote device
+ * @param congested true, if the connection is currently congested
+ * @hide
+ */
+ public void onConnectionCongested(BluetoothGatt gatt, boolean congested) {
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 34e8605..2e993c9d 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -265,6 +265,42 @@
Log.w(TAG, "Unhandled exception in callback", ex);
}
}
+
+ /**
+ * A notification/indication has been sent.
+ * @hide
+ */
+ public void onNotificationSent(String address, int status) {
+ if (DBG) Log.d(TAG, "onNotificationSent() - "
+ + "device=" + address + ", status=" + status);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ if (device == null) return;
+
+ try {
+ mCallback.onNotificationSent(device, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Callback indicating the remote device connection is congested.
+ * @hide
+ */
+ public void onConnectionCongested(String address, boolean congested) {
+ if (DBG) Log.d(TAG, "onConnectionCongested() - Device=" + address
+ + " congested=" + congested);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ if (device == null) return;
+
+ try {
+ mCallback.onConnectionCongested(device, congested);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception in callback", ex);
+ }
+ }
};
/**
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
index fc3ffe8..4fbeb46 100644
--- a/core/java/android/bluetooth/BluetoothGattServerCallback.java
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -131,4 +131,34 @@
*/
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
}
+
+ /**
+ * Callback invoked when a notification or indication has been sent to
+ * a remote device.
+ *
+ * <p>When multiple notifications are to be sent, an application must
+ * wait for this callback to be received before sending additional
+ * notifications.
+ *
+ * @param device The remote device the notification has been sent to
+ * @param status 0 if the operation was successful
+ * @hide
+ */
+ public void onNotificationSent(BluetoothDevice device, int status) {
+ }
+
+ /**
+ * Callback indicating that a remote device connection congestestion status has changed.
+ *
+ * An application should refrain from sending additional data (notifications, indications
+ * etc.) to a remote device when a callback is received with the congested flag set
+ * to true. Once the congestion status is cleared up, the application will receive an
+ * additional callback with the congested flag set to false.
+ *
+ * @param device The remote device that triggered the congestion state change
+ * @param congested true, if the connection is currently congested
+ * @hide
+ */
+ public void onConnectionCongested(BluetoothDevice device, boolean congested) {
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java
index 554df3e..252e3d2 100644
--- a/core/java/android/bluetooth/BluetoothInputDevice.java
+++ b/core/java/android/bluetooth/BluetoothInputDevice.java
@@ -640,7 +640,7 @@
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
*
* @param device Remote Bluetooth Device
- * @param data Data to send
+ * @param report Report to send
* @return false on immediate error,
* true otherwise
* @hide
diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
deleted file mode 100644
index f0c8299..0000000
--- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
+++ /dev/null
@@ -1,418 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.bluetooth;
-
-import android.net.BaseNetworkStateTracker;
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.DhcpResults;
-import android.net.NetworkCapabilities;
-import android.net.LinkProperties;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkStateTracker;
-import android.net.NetworkUtils;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.util.AsyncChannel;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * This class tracks the data connection associated with Bluetooth
- * reverse tethering. This is a singleton class and an instance will be
- * created by ConnectivityService. BluetoothService will call into this
- * when a reverse tethered connection needs to be activated.
- *
- * @hide
- */
-public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker {
- private static final String NETWORKTYPE = "BLUETOOTH_TETHER";
- private static final String TAG = "BluetoothTethering";
- private static final boolean DBG = true;
- private static final boolean VDBG = true;
-
- // Event sent to the mBtdtHandler when DHCP fails so we can tear down the network.
- private static final int EVENT_NETWORK_FAILED = 1;
-
- private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
- private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
- private AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0);
- private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
-
- private final Object mLinkPropertiesLock = new Object();
- private final Object mNetworkInfoLock = new Object();
-
- private BluetoothPan mBluetoothPan;
- private static String mRevTetheredIface;
- /* For sending events to connectivity service handler */
- private Handler mCsHandler;
- private static BluetoothTetheringDataTracker sInstance;
- private BtdtHandler mBtdtHandler;
- private AtomicReference<AsyncChannel> mAsyncChannel = new AtomicReference<AsyncChannel>(null);
-
- private BluetoothTetheringDataTracker() {
- mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_BLUETOOTH, 0, NETWORKTYPE, "");
- mLinkProperties = new LinkProperties();
- mNetworkCapabilities = new NetworkCapabilities();
-
- mNetworkInfo.setIsAvailable(false);
- setTeardownRequested(false);
- }
-
- public static synchronized BluetoothTetheringDataTracker getInstance() {
- if (sInstance == null) sInstance = new BluetoothTetheringDataTracker();
- return sInstance;
- }
-
- public Object Clone() throws CloneNotSupportedException {
- throw new CloneNotSupportedException();
- }
-
- public void setTeardownRequested(boolean isRequested) {
- mTeardownRequested.set(isRequested);
- }
-
- public boolean isTeardownRequested() {
- return mTeardownRequested.get();
- }
-
- /**
- * Begin monitoring connectivity
- */
- public void startMonitoring(Context context, Handler target) {
- if (DBG) Log.d(TAG, "startMonitoring: target: " + target);
- mContext = context;
- mCsHandler = target;
- if (VDBG) Log.d(TAG, "startMonitoring: mCsHandler: " + mCsHandler);
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- adapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.PAN);
- }
- mBtdtHandler = new BtdtHandler(target.getLooper(), this);
- }
-
- private BluetoothProfile.ServiceListener mProfileServiceListener =
- new BluetoothProfile.ServiceListener() {
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mBluetoothPan = (BluetoothPan) proxy;
- }
- public void onServiceDisconnected(int profile) {
- mBluetoothPan = null;
- }
- };
-
- /**
- * Disable connectivity to a network
- * TODO: do away with return value after making MobileDataStateTracker async
- */
- public boolean teardown() {
- mTeardownRequested.set(true);
- if (mBluetoothPan != null) {
- for (BluetoothDevice device: mBluetoothPan.getConnectedDevices()) {
- mBluetoothPan.disconnect(device);
- }
- }
- return true;
- }
-
- @Override
- public void captivePortalCheckCompleted(boolean isCaptivePortal) {
- // not implemented
- }
-
- /**
- * Re-enable connectivity to a network after a {@link #teardown()}.
- */
- public boolean reconnect() {
- mTeardownRequested.set(false);
- //Ignore
- return true;
- }
-
- /**
- * Turn the wireless radio off for a network.
- * @param turnOn {@code true} to turn the radio on, {@code false}
- */
- public boolean setRadio(boolean turnOn) {
- return true;
- }
-
- /**
- * @return true - If are we currently tethered with another device.
- */
- public synchronized boolean isAvailable() {
- return mNetworkInfo.isAvailable();
- }
-
- /**
- * Tells the underlying networking system that the caller wants to
- * begin using the named feature. The interpretation of {@code feature}
- * is completely up to each networking implementation.
- * @param feature the name of the feature to be used
- * @param callingPid the process ID of the process that is issuing this request
- * @param callingUid the user ID of the process that is issuing this request
- * @return an integer value representing the outcome of the request.
- * The interpretation of this value is specific to each networking
- * implementation+feature combination, except that the value {@code -1}
- * always indicates failure.
- * TODO: needs to go away
- */
- public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
- return -1;
- }
-
- /**
- * Tells the underlying networking system that the caller is finished
- * using the named feature. The interpretation of {@code feature}
- * is completely up to each networking implementation.
- * @param feature the name of the feature that is no longer needed.
- * @param callingPid the process ID of the process that is issuing this request
- * @param callingUid the user ID of the process that is issuing this request
- * @return an integer value representing the outcome of the request.
- * The interpretation of this value is specific to each networking
- * implementation+feature combination, except that the value {@code -1}
- * always indicates failure.
- * TODO: needs to go away
- */
- public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
- return -1;
- }
-
- @Override
- public void setUserDataEnable(boolean enabled) {
- Log.w(TAG, "ignoring setUserDataEnable(" + enabled + ")");
- }
-
- @Override
- public void setPolicyDataEnable(boolean enabled) {
- Log.w(TAG, "ignoring setPolicyDataEnable(" + enabled + ")");
- }
-
- /**
- * Check if private DNS route is set for the network
- */
- public boolean isPrivateDnsRouteSet() {
- return mPrivateDnsRouteSet.get();
- }
-
- /**
- * Set a flag indicating private DNS route is set
- */
- public void privateDnsRouteSet(boolean enabled) {
- mPrivateDnsRouteSet.set(enabled);
- }
-
- /**
- * Fetch NetworkInfo for the network
- */
- public NetworkInfo getNetworkInfo() {
- synchronized (mNetworkInfoLock) {
- return new NetworkInfo(mNetworkInfo);
- }
- }
-
- /**
- * Fetch LinkProperties for the network
- */
- public LinkProperties getLinkProperties() {
- synchronized (mLinkPropertiesLock) {
- return new LinkProperties(mLinkProperties);
- }
- }
-
- /**
- * Fetch default gateway address for the network
- */
- public int getDefaultGatewayAddr() {
- return mDefaultGatewayAddr.get();
- }
-
- /**
- * Check if default route is set
- */
- public boolean isDefaultRouteSet() {
- return mDefaultRouteSet.get();
- }
-
- /**
- * Set a flag indicating default route is set for the network
- */
- public void defaultRouteSet(boolean enabled) {
- mDefaultRouteSet.set(enabled);
- }
-
- /**
- * Return the system properties name associated with the tcp buffer sizes
- * for this network.
- */
- public String getTcpBufferSizesPropName() {
- return "net.tcp.buffersize.wifi";
- }
-
- private static short countPrefixLength(byte [] mask) {
- short count = 0;
- for (byte b : mask) {
- for (int i = 0; i < 8; ++i) {
- if ((b & (1 << i)) != 0) {
- ++count;
- }
- }
- }
- return count;
- }
-
- void startReverseTether(final LinkProperties linkProperties) {
- if (linkProperties == null || TextUtils.isEmpty(linkProperties.getInterfaceName())) {
- Log.e(TAG, "attempted to reverse tether with empty interface");
- return;
- }
- synchronized (mLinkPropertiesLock) {
- if (mLinkProperties.getInterfaceName() != null) {
- Log.e(TAG, "attempted to reverse tether while already in process");
- return;
- }
- mLinkProperties = linkProperties;
- }
- Thread dhcpThread = new Thread(new Runnable() {
- public void run() {
- //Currently this thread runs independently.
- DhcpResults dhcpResults = new DhcpResults();
- boolean success = NetworkUtils.runDhcp(linkProperties.getInterfaceName(),
- dhcpResults);
- synchronized (mLinkPropertiesLock) {
- if (linkProperties.getInterfaceName() != mLinkProperties.getInterfaceName()) {
- Log.e(TAG, "obsolete DHCP run aborted");
- return;
- }
- if (!success) {
- Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError());
- mBtdtHandler.obtainMessage(EVENT_NETWORK_FAILED).sendToTarget();
- return;
- }
- mLinkProperties = dhcpResults.linkProperties;
- synchronized (mNetworkInfoLock) {
- mNetworkInfo.setIsAvailable(true);
- mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
- if (mCsHandler != null) {
- Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED,
- new NetworkInfo(mNetworkInfo));
- msg.sendToTarget();
- }
- }
- return;
- }
- }
- });
- dhcpThread.start();
- }
-
- void stopReverseTether() {
- synchronized (mLinkPropertiesLock) {
- if (TextUtils.isEmpty(mLinkProperties.getInterfaceName())) {
- Log.e(TAG, "attempted to stop reverse tether with nothing tethered");
- return;
- }
- NetworkUtils.stopDhcp(mLinkProperties.getInterfaceName());
- mLinkProperties.clear();
- synchronized (mNetworkInfoLock) {
- mNetworkInfo.setIsAvailable(false);
- mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
-
- if (mCsHandler != null) {
- mCsHandler.obtainMessage(EVENT_STATE_CHANGED, new NetworkInfo(mNetworkInfo)).
- sendToTarget();
- }
- }
- }
- }
-
- public void setDependencyMet(boolean met) {
- // not supported on this network
- }
-
- @Override
- public void addStackedLink(LinkProperties link) {
- mLinkProperties.addStackedLink(link);
- }
-
- @Override
- public void removeStackedLink(LinkProperties link) {
- mLinkProperties.removeStackedLink(link);
- }
-
- static class BtdtHandler extends Handler {
- private AsyncChannel mStackChannel;
- private final BluetoothTetheringDataTracker mBtdt;
-
- BtdtHandler(Looper looper, BluetoothTetheringDataTracker parent) {
- super(looper);
- mBtdt = parent;
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
- if (VDBG) Log.d(TAG, "got CMD_CHANNEL_HALF_CONNECTED");
- if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
- AsyncChannel ac = (AsyncChannel)msg.obj;
- if (mBtdt.mAsyncChannel.compareAndSet(null, ac) == false) {
- Log.e(TAG, "Trying to set mAsyncChannel twice!");
- } else {
- ac.sendMessage(
- AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
- }
- }
- break;
- case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
- if (VDBG) Log.d(TAG, "got CMD_CHANNEL_DISCONNECTED");
- mBtdt.stopReverseTether();
- mBtdt.mAsyncChannel.set(null);
- break;
- case NetworkStateTracker.EVENT_NETWORK_CONNECTED:
- LinkProperties linkProperties = (LinkProperties)(msg.obj);
- if (VDBG) Log.d(TAG, "got EVENT_NETWORK_CONNECTED, " + linkProperties);
- mBtdt.startReverseTether(linkProperties);
- break;
- case NetworkStateTracker.EVENT_NETWORK_DISCONNECTED:
- linkProperties = (LinkProperties)(msg.obj);
- if (VDBG) Log.d(TAG, "got EVENT_NETWORK_DISCONNECTED, " + linkProperties);
- mBtdt.stopReverseTether();
- break;
- case EVENT_NETWORK_FAILED:
- if (VDBG) Log.d(TAG, "got EVENT_NETWORK_FAILED");
- mBtdt.teardown();
- break;
- }
- }
- }
-
- @Override
- public void supplyMessenger(Messenger messenger) {
- if (messenger != null) {
- new AsyncChannel().connect(mContext, mBtdtHandler, messenger);
- }
- }
-}
diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
index 2d8eed4..946a6f6 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -66,4 +66,5 @@
void onAdvertiseStateChange(in int advertiseState, in int status);
void onMultiAdvertiseCallback(in int status);
void onConfigureMTU(in String address, in int mtu, in int status);
+ void onConnectionCongested(in String address, in boolean congested);
}
diff --git a/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
index ae9bffc..6e31da1 100644
--- a/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
@@ -22,7 +22,7 @@
* Callback definitions for interacting with BLE / GATT
* @hide
*/
-interface IBluetoothGattServerCallback {
+oneway interface IBluetoothGattServerCallback {
void onServerRegistered(in int status, in int serverIf);
void onScanResult(in String address, in int rssi, in byte[] advData);
void onServerConnectionState(in int status, in int serverIf,
@@ -58,4 +58,6 @@
in ParcelUuid descrId,
in byte[] value);
void onExecuteWrite(in String address, in int transId, in boolean execWrite);
+ void onNotificationSent(in String address, in int status);
+ void onConnectionCongested(in String address, in boolean congested);
}
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index ed43407..c20b81b 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -355,6 +355,11 @@
public void onConfigureMTU(String address, int mtu, int status) {
// no op
}
+
+ @Override
+ public void onConnectionCongested(String address, boolean congested) {
+ // no op
+ }
}
private void postCallbackFailure(final AdvertiseCallback callback, final int error) {
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index 4c6346c..fbaf5d2 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -358,6 +358,11 @@
public void onConfigureMTU(String address, int mtu, int status) {
// no op
}
+
+ @Override
+ public void onConnectionCongested(String address, boolean congested) {
+ // no op
+ }
}
private void postCallbackError(final ScanCallback callback, final int errorCode) {
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 392bfbc..be70411 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1751,6 +1751,15 @@
* @param extras any extras to pass to the SyncAdapter.
*/
public static void requestSync(Account account, String authority, Bundle extras) {
+ requestSyncAsUser(account, authority, UserHandle.getCallingUserId(), extras);
+ }
+
+ /**
+ * @see #requestSync(Account, String, Bundle)
+ * @hide
+ */
+ public static void requestSyncAsUser(Account account, String authority, int userId,
+ Bundle extras) {
if (extras == null) {
throw new IllegalArgumentException("Must specify extras.");
}
@@ -1760,7 +1769,11 @@
.setExtras(extras)
.syncOnce() // Immediate sync.
.build();
- requestSync(request);
+ try {
+ getContentService().syncAsUser(request, userId);
+ } catch(RemoteException e) {
+ // Shouldn't happen.
+ }
}
/**
@@ -1839,6 +1852,17 @@
}
/**
+ * @see #cancelSync(Account, String)
+ * @hide
+ */
+ public static void cancelSyncAsUser(Account account, String authority, int userId) {
+ try {
+ getContentService().cancelSyncAsUser(account, authority, null, userId);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Get information about the SyncAdapters that are known to the system.
* @return an array of SyncAdapters that have registered with the system
*/
@@ -1851,6 +1875,18 @@
}
/**
+ * @see #getSyncAdapterTypes()
+ * @hide
+ */
+ public static SyncAdapterType[] getSyncAdapterTypesAsUser(int userId) {
+ try {
+ return getContentService().getSyncAdapterTypesAsUser(userId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
* Check if the provider should be synced when a network tickle is received
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#READ_SYNC_SETTINGS}.
@@ -1868,6 +1904,19 @@
}
/**
+ * @see #getSyncAutomatically(Account, String)
+ * @hide
+ */
+ public static boolean getSyncAutomaticallyAsUser(Account account, String authority,
+ int userId) {
+ try {
+ return getContentService().getSyncAutomaticallyAsUser(account, authority, userId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
* Set whether or not the provider is synced when it receives a network tickle.
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
@@ -2032,6 +2081,18 @@
}
/**
+ * @see #getIsSyncable(Account, String)
+ * @hide
+ */
+ public static int getIsSyncableAsUser(Account account, String authority, int userId) {
+ try {
+ return getContentService().getIsSyncableAsUser(account, authority, userId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
* Set whether this account/provider is syncable.
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
@@ -2063,6 +2124,18 @@
}
/**
+ * @see #getMasterSyncAutomatically()
+ * @hide
+ */
+ public static boolean getMasterSyncAutomaticallyAsUser(int userId) {
+ try {
+ return getContentService().getMasterSyncAutomaticallyAsUser(userId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
* Sets the master auto-sync setting that applies to all the providers and accounts.
* If this is false then the per-provider auto-sync setting is ignored.
* <p>This method requires the caller to hold the permission
@@ -2147,6 +2220,18 @@
}
/**
+ * @see #getCurrentSyncs()
+ * @hide
+ */
+ public static List<SyncInfo> getCurrentSyncsAsUser(int userId) {
+ try {
+ return getContentService().getCurrentSyncsAsUser(userId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
* Returns the status that matches the authority.
* @param account the account whose setting we are querying
* @param authority the provider whose behavior is being queried
@@ -2162,6 +2247,19 @@
}
/**
+ * @see #getSyncStatus(Account, String)
+ * @hide
+ */
+ public static SyncStatusInfo getSyncStatusAsUser(Account account, String authority,
+ int userId) {
+ try {
+ return getContentService().getSyncStatusAsUser(account, authority, null, userId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("the ContentService should always be reachable", e);
+ }
+ }
+
+ /**
* Return true if the pending status is true of any matching authorities.
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#READ_SYNC_STATS}.
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index 373f2fb..1e96713 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -60,7 +60,9 @@
* Start a sync given a request.
*/
void sync(in SyncRequest request);
+ void syncAsUser(in SyncRequest request, int userId);
void cancelSync(in Account account, String authority, in ComponentName cname);
+ void cancelSyncAsUser(in Account account, String authority, in ComponentName cname, int userId);
/** Cancel a sync, providing information about the sync to be cancelled. */
void cancelRequest(in SyncRequest request);
@@ -71,6 +73,7 @@
* @return true if the provider should be synced when a network tickle is received
*/
boolean getSyncAutomatically(in Account account, String providerName);
+ boolean getSyncAutomaticallyAsUser(in Account account, String providerName, int userId);
/**
* Set whether or not the provider is synced when it receives a network tickle.
@@ -114,6 +117,7 @@
* @return >0 if it is syncable, 0 if not, and <0 if the state isn't known yet.
*/
int getIsSyncable(in Account account, String providerName);
+ int getIsSyncableAsUser(in Account account, String providerName, int userId);
/**
* Set whether this account/provider is syncable.
@@ -124,14 +128,17 @@
void setMasterSyncAutomatically(boolean flag);
boolean getMasterSyncAutomatically();
+ boolean getMasterSyncAutomaticallyAsUser(int userId);
List<SyncInfo> getCurrentSyncs();
+ List<SyncInfo> getCurrentSyncsAsUser(int userId);
/**
* Returns the types of the SyncAdapters that are registered with the system.
* @return Returns the types of the SyncAdapters that are registered with the system.
*/
SyncAdapterType[] getSyncAdapterTypes();
+ SyncAdapterType[] getSyncAdapterTypesAsUser(int userId);
/**
* Returns true if there is currently a operation for the given account/authority or service
@@ -152,6 +159,8 @@
* non-null.
*/
SyncStatusInfo getSyncStatus(in Account account, String authority, in ComponentName cname);
+ SyncStatusInfo getSyncStatusAsUser(in Account account, String authority, in ComponentName cname,
+ int userId);
/**
* Return true if the pending status is true of any matching authorities.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index d11698c..9ac433f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -198,6 +198,12 @@
public static final int MATCH_DEFAULT_ONLY = 0x00010000;
/**
+ * Resolution and querying flag: do not resolve intents cross-profile.
+ * @hide
+ */
+ public static final int NO_CROSS_PROFILE = 0x00020000;
+
+ /**
* Flag for {@link addCrossProfileIntentFilter}: if the cross-profile intent has been set by the
* profile owner.
* @hide
@@ -2310,6 +2316,7 @@
* @see #MATCH_DEFAULT_ONLY
* @see #GET_INTENT_FILTERS
* @see #GET_RESOLVED_FILTER
+ * @see #NO_CROSS_PROFILE
* @hide
*/
public abstract List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
@@ -2471,6 +2478,19 @@
int flags);
/**
+ * Find a single content provider by its base path name.
+ *
+ * @param name The name of the provider to find.
+ * @param flags Additional option flags. Currently should always be 0.
+ * @param userId The user id.
+ *
+ * @return ContentProviderInfo Information about the provider, if found,
+ * else null.
+ * @hide
+ */
+ public abstract ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId);
+
+ /**
* Retrieve content provider information.
*
* <p><em>Note: unlike most other methods, an empty result set is indicated
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index e2f88eb..14e6f92 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -466,6 +466,34 @@
new Key<Integer>("android.control.maxRegionsAf", int.class);
/**
+ * <p>List of available high speed video size and fps range configurations
+ * supported by the camera device, in the format of (width, height, fps_min, fps_max).</p>
+ * <p>When HIGH_SPEED_VIDEO is supported in {@link CameraCharacteristics#CONTROL_AVAILABLE_SCENE_MODES android.control.availableSceneModes},
+ * this metadata will list the supported high speed video size and fps range
+ * configurations. All the sizes listed in this configuration will be a subset
+ * of the sizes reported by StreamConfigurationMap#getOutputSizes for processed
+ * non-stalling formats.</p>
+ * <p>For the high speed video use case, where the application will set
+ * {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} to HIGH_SPEED_VIDEO in capture requests, the application must
+ * select the video size and fps range from this metadata to configure the recording and
+ * preview streams and setup the recording requests. For example, if the application intends
+ * to do high speed recording, it can select the maximum size reported by this metadata to
+ * configure output streams. Once the size is selected, application can filter this metadata
+ * by selected size and get the supported fps ranges, and use these fps ranges to setup the
+ * recording requests.</p>
+ * <p>For normal video recording use case, where some application will NOT set
+ * {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} to HIGH_SPEED_VIDEO in capture requests, the fps ranges
+ * reported in this metadata must not be used to setup capture requests, or it will cause
+ * request error.</p>
+ *
+ * @see CameraCharacteristics#CONTROL_AVAILABLE_SCENE_MODES
+ * @see CaptureRequest#CONTROL_SCENE_MODE
+ * @hide
+ */
+ public static final Key<int[]> CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS =
+ new Key<int[]>("android.control.availableHighSpeedVideoConfigurations", int[].class);
+
+ /**
* <p>The set of edge enhancement modes supported by this camera device.</p>
* <p>This tag lists the valid modes for {@link CaptureRequest#EDGE_MODE android.edge.mode}.</p>
* <p>Full-capability camera devices must always support OFF and FAST.</p>
@@ -1354,7 +1382,11 @@
/**
* <p>Range of valid exposure
* times used by {@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}.</p>
+ * <p>The min value will be <= 100e3 (100 us). For FULL
+ * capability devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} == FULL),
+ * max will be >= 100e6 (100ms)</p>
*
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see CaptureRequest#SENSOR_EXPOSURE_TIME
*/
public static final Key<android.util.Range<Long>> SENSOR_INFO_EXPOSURE_TIME_RANGE =
@@ -1371,7 +1403,10 @@
* <p>Refer to
* StreamConfigurationMap#getOutputMinFrameDuration(int,Size)
* for the minimum frame duration values.</p>
+ * <p>For FULL capability devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} == FULL),
+ * max will be >= 100e6 (100ms).</p>
*
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see CaptureRequest#SENSOR_FRAME_DURATION
*/
public static final Key<Long> SENSOR_INFO_MAX_FRAME_DURATION =
@@ -1748,18 +1783,26 @@
/**
* <p>Generally classifies the overall set of the camera device functionality.</p>
* <p>Camera devices will come in two flavors: LIMITED and FULL.</p>
- * <p>A FULL device has the most support possible and will enable the
- * widest range of use cases such as:</p>
+ * <p>A FULL device has the most support possible and will support below capabilities:</p>
* <ul>
* <li>30fps at maximum resolution (== sensor resolution) is preferred, more than 20fps is required.</li>
* <li>Per frame control ({@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} <code>==</code> PER_FRAME_CONTROL)</li>
* <li>Manual sensor control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_SENSOR)</li>
* <li>Manual post-processing control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_POST_PROCESSING)</li>
+ * <li>Arbitrary cropping region ({@link CameraCharacteristics#SCALER_CROPPING_TYPE android.scaler.croppingType} <code>==</code> FREEFORM)</li>
+ * <li>At least 3 processed (but not stalling) format output streams ({@link CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_PROC android.request.maxNumOutputProc} <code>>=</code> 3)</li>
+ * <li>The required stream configuration defined in android.scaler.availableStreamConfigurations</li>
+ * <li>The required exposure time range defined in {@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}</li>
+ * <li>The required maxFrameDuration defined in {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li>
* </ul>
* <p>A LIMITED device may have some or none of the above characteristics.
* To find out more refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}.</p>
*
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ * @see CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_PROC
+ * @see CameraCharacteristics#SCALER_CROPPING_TYPE
+ * @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE
+ * @see CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION
* @see CameraCharacteristics#SYNC_MAX_LATENCY
* @see #INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
* @see #INFO_SUPPORTED_HARDWARE_LEVEL_FULL
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index dad1854..e464f2a 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -310,7 +310,9 @@
* <li>{@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains}</li>
* </ul>
* </li>
- * <li>Lens shading map information<ul>
+ * <li>Manual lens shading map control<ul>
+ * <li>{@link CaptureRequest#SHADING_MODE android.shading.mode}</li>
+ * <li>{@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode}</li>
* <li>android.statistics.lensShadingMap</li>
* <li>android.lens.info.shadingMapSize</li>
* </ul>
@@ -323,6 +325,8 @@
*
* @see CaptureRequest#COLOR_CORRECTION_GAINS
* @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
+ * @see CaptureRequest#SHADING_MODE
+ * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
* @see CaptureRequest#TONEMAP_CURVE
* @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
* @see CaptureRequest#TONEMAP_MODE
@@ -1389,6 +1393,84 @@
*/
public static final int CONTROL_SCENE_MODE_BARCODE = 16;
+ /**
+ * <p>Optimized for high speed video recording (frame rate >=60fps) use case.</p>
+ * <p>The supported high speed video sizes and fps ranges are specified in
+ * android.control.availableHighSpeedVideoConfigurations. To get desired
+ * output frame rates, the application is only allowed to select video size
+ * and fps range combinations listed in this static metadata. The fps range
+ * can be control via {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}.</p>
+ * <p>In this mode, the camera device will override aeMode, awbMode, and afMode to
+ * ON, ON, and CONTINUOUS_VIDEO, respectively. All post-processing block mode
+ * controls will be overridden to be FAST. Therefore, no manual control of capture
+ * and post-processing parameters is possible. All other controls operate the
+ * same as when {@link CaptureRequest#CONTROL_MODE android.control.mode} == AUTO. This means that all other
+ * android.control.* fields continue to work, such as</p>
+ * <ul>
+ * <li>{@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange}</li>
+ * <li>{@link CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION android.control.aeExposureCompensation}</li>
+ * <li>{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock}</li>
+ * <li>{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock}</li>
+ * <li>{@link CaptureRequest#CONTROL_EFFECT_MODE android.control.effectMode}</li>
+ * <li>{@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}</li>
+ * <li>{@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}</li>
+ * <li>{@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}</li>
+ * <li>{@link CaptureRequest#CONTROL_AF_TRIGGER android.control.afTrigger}</li>
+ * <li>{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger}</li>
+ * </ul>
+ * <p>Outside of android.control.*, the following controls will work:</p>
+ * <ul>
+ * <li>{@link CaptureRequest#FLASH_MODE android.flash.mode} (automatic flash for still capture will not work since aeMode is ON)</li>
+ * <li>{@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode} (if it is supported)</li>
+ * <li>{@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}</li>
+ * <li>{@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode}</li>
+ * </ul>
+ * <p>For high speed recording use case, the actual maximum supported frame rate may
+ * be lower than what camera can output, depending on the destination Surfaces for
+ * the image data. For example, if the destination surface is from video encoder,
+ * the application need check if the video encoder is capable of supporting the
+ * high frame rate for a given video size, or it will end up with lower recording
+ * frame rate. If the destination surface is from preview window, the preview frame
+ * rate will be bounded by the screen refresh rate.</p>
+ * <p>The camera device will only support up to 2 output high speed streams
+ * (processed non-stalling format defined in android.request.maxNumOutputStreams)
+ * in this mode. This control will be effective only if all of below conditions are true:</p>
+ * <ul>
+ * <li>The application created no more than maxNumHighSpeedStreams processed non-stalling
+ * format output streams, where maxNumHighSpeedStreams is calculated as
+ * min(2, android.request.maxNumOutputStreams[Processed (but not-stalling)]).</li>
+ * <li>The stream sizes are selected from the sizes reported by
+ * android.control.availableHighSpeedVideoConfigurations.</li>
+ * <li>No processed non-stalling or raw streams are configured.</li>
+ * </ul>
+ * <p>When above conditions are NOT satistied, the controls of this mode and
+ * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange} will be ignored by the camera device,
+ * the camera device will fall back to {@link CaptureRequest#CONTROL_MODE android.control.mode} <code>==</code> AUTO,
+ * and the returned capture result metadata will give the fps range choosen
+ * by the camera device.</p>
+ * <p>Switching into or out of this mode may trigger some camera ISP/sensor
+ * reconfigurations, which may introduce extra latency. It is recommended that
+ * the application avoids unnecessary scene mode switch as much as possible.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_EXPOSURE_COMPENSATION
+ * @see CaptureRequest#CONTROL_AE_LOCK
+ * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
+ * @see CaptureRequest#CONTROL_AE_REGIONS
+ * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
+ * @see CaptureRequest#CONTROL_AF_REGIONS
+ * @see CaptureRequest#CONTROL_AF_TRIGGER
+ * @see CaptureRequest#CONTROL_AWB_LOCK
+ * @see CaptureRequest#CONTROL_AWB_REGIONS
+ * @see CaptureRequest#CONTROL_EFFECT_MODE
+ * @see CaptureRequest#CONTROL_MODE
+ * @see CaptureRequest#FLASH_MODE
+ * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see CaptureRequest#STATISTICS_FACE_DETECT_MODE
+ * @see CaptureRequest#CONTROL_SCENE_MODE
+ */
+ public static final int CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO = 17;
+
//
// Enumeration values for CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE
//
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 5bc59dc..91ff7fa 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1011,6 +1011,7 @@
* @see #CONTROL_SCENE_MODE_PARTY
* @see #CONTROL_SCENE_MODE_CANDLELIGHT
* @see #CONTROL_SCENE_MODE_BARCODE
+ * @see #CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO
*/
public static final Key<Integer> CONTROL_SCENE_MODE =
new Key<Integer>("android.control.sceneMode", int.class);
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 9097220..be2d960 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -1581,6 +1581,7 @@
* @see #CONTROL_SCENE_MODE_PARTY
* @see #CONTROL_SCENE_MODE_CANDLELIGHT
* @see #CONTROL_SCENE_MODE_BARCODE
+ * @see #CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO
*/
public static final Key<Integer> CONTROL_SCENE_MODE =
new Key<Integer>("android.control.sceneMode", int.class);
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index abd69c1..03dd354 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -17,7 +17,9 @@
package android.hardware.camera2.legacy;
import android.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
@@ -25,7 +27,9 @@
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
+import android.os.ConditionVariable;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
@@ -33,7 +37,6 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* Compatibility implementation of the Camera2 API binder interface.
@@ -53,6 +56,7 @@
private static final String TAG = "CameraDeviceUserShim";
private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
+ private static final int OPEN_CAMERA_TIMEOUT_MS = 5000; // 5 sec (same as api1 cts timeout)
private final LegacyCameraDevice mLegacyDevice;
@@ -60,29 +64,143 @@
private int mSurfaceIdCounter;
private boolean mConfiguring;
private final SparseArray<Surface> mSurfaces;
+ private final CameraCharacteristics mCameraCharacteristics;
+ private final CameraLooper mCameraInit;
- protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera) {
+ protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera,
+ CameraCharacteristics characteristics, CameraLooper cameraInit) {
mLegacyDevice = legacyCamera;
mConfiguring = false;
mSurfaces = new SparseArray<Surface>();
+ mCameraCharacteristics = characteristics;
+ mCameraInit = cameraInit;
mSurfaceIdCounter = 0;
}
+ /**
+ * Create a separate looper/thread for the camera to run on; open the camera.
+ *
+ * <p>Since the camera automatically latches on to the current thread's looper,
+ * it's important that we have our own thread with our own looper to guarantee
+ * that the camera callbacks get correctly posted to our own thread.</p>
+ */
+ private static class CameraLooper implements Runnable, AutoCloseable {
+ private final int mCameraId;
+ private Looper mLooper;
+ private volatile int mInitErrors;
+ private final Camera mCamera = Camera.openUninitialized();
+ private final ConditionVariable mStartDone = new ConditionVariable();
+ private final Thread mThread;
+
+ /**
+ * Spin up a new thread, immediately open the camera in the background.
+ *
+ * <p>Use {@link #waitForOpen} to block until the camera is finished opening.</p>
+ *
+ * @param cameraId numeric camera Id
+ *
+ * @see #waitForOpen
+ */
+ public CameraLooper(int cameraId) {
+ mCameraId = cameraId;
+
+ mThread = new Thread(this);
+ mThread.start();
+ }
+
+ public Camera getCamera() {
+ return mCamera;
+ }
+
+ @Override
+ public void run() {
+ // Set up a looper to be used by camera.
+ Looper.prepare();
+
+ // Save the looper so that we can terminate this thread
+ // after we are done with it.
+ mLooper = Looper.myLooper();
+ mInitErrors = mCamera.cameraInitUnspecified(mCameraId);
+
+ mStartDone.open();
+ Looper.loop(); // Blocks forever until #close is called.
+ }
+
+ /**
+ * Quit the looper safely; then join until the thread shuts down.
+ */
+ @Override
+ public void close() {
+ if (mLooper == null) {
+ return;
+ }
+
+ mLooper.quitSafely();
+ try {
+ mThread.join();
+ } catch (InterruptedException e) {
+ throw new AssertionError(e);
+ }
+
+ mLooper = null;
+ }
+
+ /**
+ * Block until the camera opens; then return its initialization error code (if any).
+ *
+ * @param timeoutMs timeout in milliseconds
+ *
+ * @return int error code
+ *
+ * @throws CameraRuntimeException if the camera open times out with ({@code CAMERA_ERROR})
+ */
+ public int waitForOpen(int timeoutMs) {
+ // Block until the camera is open asynchronously
+ if (!mStartDone.block(timeoutMs)) {
+ Log.e(TAG, "waitForOpen - Camera failed to open after timeout of "
+ + OPEN_CAMERA_TIMEOUT_MS + " ms");
+ try {
+ mCamera.release();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "connectBinderShim - Failed to release camera after timeout ", e);
+ }
+
+ throw new CameraRuntimeException(CameraAccessException.CAMERA_ERROR);
+ }
+
+ return mInitErrors;
+ }
+ }
+
public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
int cameraId) {
if (DEBUG) {
Log.d(TAG, "Opening shim Camera device");
}
- // TODO: Move open/init into LegacyCameraDevice thread when API is switched to async.
- Camera legacyCamera = Camera.openUninitialized();
- int initErrors = legacyCamera.cameraInitUnspecified(cameraId);
+
+ /*
+ * Put the camera open on a separate thread with its own looper; otherwise
+ * if the main thread is used then the callbacks might never get delivered
+ * (e.g. in CTS which run its own default looper only after tests)
+ */
+
+ CameraLooper init = new CameraLooper(cameraId);
+
+ // TODO: Make this async instead of blocking
+ int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS);
+ Camera legacyCamera = init.getCamera();
// Check errors old HAL initialization
CameraBinderDecorator.throwOnError(initErrors);
+ CameraInfo info = new CameraInfo();
+ Camera.getCameraInfo(cameraId, info);
+
+ CameraCharacteristics characteristics =
+ LegacyMetadataMapper.createCharacteristics(legacyCamera.getParameters(), info);
LegacyCameraDevice device = new LegacyCameraDevice(cameraId, legacyCamera, callbacks);
- return new CameraDeviceUserShim(cameraId, device);
+ return new CameraDeviceUserShim(cameraId, device, characteristics, init);
}
@Override
@@ -90,7 +208,12 @@
if (DEBUG) {
Log.d(TAG, "disconnect called.");
}
- mLegacyDevice.close();
+
+ try {
+ mLegacyDevice.close();
+ } finally {
+ mCameraInit.close();
+ }
}
@Override
@@ -218,8 +341,17 @@
if (DEBUG) {
Log.d(TAG, "createDefaultRequest called.");
}
- // TODO: implement createDefaultRequest.
- Log.e(TAG, "createDefaultRequest unimplemented.");
+
+ CameraMetadataNative template;
+ try {
+ template =
+ LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "createDefaultRequest - invalid templateId specified");
+ return CameraBinderDecorator.BAD_VALUE;
+ }
+
+ request.swap(template);
return CameraBinderDecorator.NO_ERROR;
}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index cb951b3..4b39092 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -29,12 +29,15 @@
import android.os.HandlerThread;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Size;
import android.view.Surface;
import java.util.ArrayList;
import java.util.List;
+import static android.hardware.camera2.legacy.LegacyExceptionUtils.*;
import static android.hardware.camera2.utils.CameraBinderDecorator.*;
+import static com.android.internal.util.Preconditions.*;
/**
* This class emulates the functionality of a Camera2 device using a the old Camera class.
@@ -181,8 +184,8 @@
* @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888} or a compatible
* format.
*/
- static boolean needsConversion(Surface s) {
- int nativeType = LegacyCameraDevice.nativeDetectSurfaceType(s);
+ static boolean needsConversion(Surface s) throws BufferQueueAbandonedException {
+ int nativeType = detectSurfaceType(s);
return nativeType == ImageFormat.YUV_420_888 || nativeType == ImageFormat.YV12 ||
nativeType == ImageFormat.NV21;
}
@@ -367,18 +370,79 @@
}
}
- protected static native int nativeDetectSurfaceType(Surface surface);
+ /**
+ * Query the surface for its currently configured default buffer size.
+ * @param surface a non-{@code null} {@code Surface}
+ * @return the width and height of the surface
+ *
+ * @throws NullPointerException if the {@code surface} was {@code null}
+ * @throws IllegalStateException if the {@code surface} was invalid
+ */
+ static Size getSurfaceSize(Surface surface) throws BufferQueueAbandonedException {
+ checkNotNull(surface);
- protected static native void nativeDetectSurfaceDimens(Surface surface, int[] dimens);
+ int[] dimens = new int[2];
+ LegacyExceptionUtils.throwOnError(nativeDetectSurfaceDimens(surface, /*out*/dimens));
- protected static native void nativeConfigureSurface(Surface surface, int width, int height,
+ return new Size(dimens[0], dimens[1]);
+ }
+
+ static int detectSurfaceType(Surface surface) throws BufferQueueAbandonedException {
+ checkNotNull(surface);
+ return LegacyExceptionUtils.throwOnError(nativeDetectSurfaceType(surface));
+ }
+
+ static void configureSurface(Surface surface, int width, int height,
+ int pixelFormat) throws BufferQueueAbandonedException {
+ checkNotNull(surface);
+ checkArgumentPositive(width, "width must be positive.");
+ checkArgumentPositive(height, "height must be positive.");
+
+ LegacyExceptionUtils.throwOnError(nativeConfigureSurface(surface, width, height,
+ pixelFormat));
+ }
+
+ static void produceFrame(Surface surface, byte[] pixelBuffer, int width,
+ int height, int pixelFormat)
+ throws BufferQueueAbandonedException {
+ checkNotNull(surface);
+ checkNotNull(pixelBuffer);
+ checkArgumentPositive(width, "width must be positive.");
+ checkArgumentPositive(height, "height must be positive.");
+
+ LegacyExceptionUtils.throwOnError(nativeProduceFrame(surface, pixelBuffer, width, height,
+ pixelFormat));
+ }
+
+ static void setSurfaceFormat(Surface surface, int pixelFormat)
+ throws BufferQueueAbandonedException {
+ checkNotNull(surface);
+
+ LegacyExceptionUtils.throwOnError(nativeSetSurfaceFormat(surface, pixelFormat));
+ }
+
+ static void setSurfaceDimens(Surface surface, int width, int height)
+ throws BufferQueueAbandonedException {
+ checkNotNull(surface);
+ checkArgumentPositive(width, "width must be positive.");
+ checkArgumentPositive(height, "height must be positive.");
+
+ LegacyExceptionUtils.throwOnError(nativeSetSurfaceDimens(surface, width, height));
+ }
+
+ private static native int nativeDetectSurfaceType(Surface surface);
+
+ private static native int nativeDetectSurfaceDimens(Surface surface,
+ /*out*/int[/*2*/] dimens);
+
+ private static native int nativeConfigureSurface(Surface surface, int width, int height,
int pixelFormat);
- protected static native void nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
+ private static native int nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
int height, int pixelFormat);
- protected static native void nativeSetSurfaceFormat(Surface surface, int pixelFormat);
+ private static native int nativeSetSurfaceFormat(Surface surface, int pixelFormat);
- protected static native void nativeSetSurfaceDimens(Surface surface, int width, int height);
+ private static native int nativeSetSurfaceDimens(Surface surface, int width, int height);
}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java b/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java
new file mode 100644
index 0000000..7e0c01b
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java
@@ -0,0 +1,77 @@
+/*
+ * 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.hardware.camera2.legacy;
+
+import android.hardware.camera2.utils.CameraBinderDecorator;
+import android.util.AndroidException;
+
+/**
+ * Utility class containing exception handling used solely by the compatibility mode shim.
+ */
+public class LegacyExceptionUtils {
+ private static final String TAG = "LegacyExceptionUtils";
+
+ /**
+ * Checked exception thrown when a BufferQueue has been abandoned by its consumer.
+ */
+ public static class BufferQueueAbandonedException extends AndroidException {
+ public BufferQueueAbandonedException () {}
+
+ public BufferQueueAbandonedException(String name) {
+ super(name);
+ }
+
+ public BufferQueueAbandonedException(String name, Throwable cause) {
+ super(name, cause);
+ }
+
+ public BufferQueueAbandonedException(Exception cause) {
+ super(cause);
+ }
+ }
+
+ /**
+ * Throw error codes used by legacy device methods as exceptions.
+ *
+ * <p>Non-negative return values are passed through, negative return values are thrown as
+ * exceptions.</p>
+ *
+ * @param errorFlag error to throw as an exception.
+ * @throws {@link BufferQueueAbandonedException} for {@link CameraBinderDecorator#ENODEV}.
+ * @throws {@link UnsupportedOperationException} for an unknown negative error code.
+ * @return {@code errorFlag} if the value was non-negative, throws otherwise.
+ */
+ public static int throwOnError(int errorFlag) throws BufferQueueAbandonedException {
+ switch (errorFlag) {
+ case CameraBinderDecorator.NO_ERROR: {
+ return CameraBinderDecorator.NO_ERROR;
+ }
+ case CameraBinderDecorator.ENODEV: {
+ throw new BufferQueueAbandonedException();
+ }
+ }
+
+ if (errorFlag < 0) {
+ throw new UnsupportedOperationException("Unknown error " + errorFlag);
+ }
+ return errorFlag;
+ }
+
+ private LegacyExceptionUtils() {
+ throw new AssertionError();
+ }
+}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index 1cde2c4..4797e20 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -19,8 +19,11 @@
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.impl.CameraMetadataNative;
@@ -32,6 +35,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import static com.android.internal.util.Preconditions.*;
import static android.hardware.camera2.CameraCharacteristics.*;
@@ -44,6 +48,8 @@
private static final String TAG = "LegacyMetadataMapper";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+ private static final long NS_PER_MS = 1000000;
+
// from graphics.h
private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
private static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
@@ -51,10 +57,57 @@
// for metadata
private static final float LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS = 0.0f;
- private static final long APPROXIMATE_CAPTURE_DELAY_MS = 200; // ms
- private static final long APPROXIMATE_SENSOR_AREA = (1 << 23); // 8mp
- private static final long APPROXIMATE_JPEG_ENCODE_TIME = 600; // ms
- private static final long NS_PER_MS = 1000000;
+ private static final int REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_RAW = 0; // no raw support
+ private static final int REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC = 3; // preview, video, cb
+ private static final int REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC_STALL = 1; // 1 jpeg only
+ private static final int REQUEST_MAX_NUM_INPUT_STREAMS_COUNT = 0; // no reprocessing
+
+ /** Assume 3 HAL1 stages: Exposure, Read-out, Post-Processing */
+ private static final int REQUEST_PIPELINE_MAX_DEPTH_HAL1 = 3;
+ /** Assume 3 shim stages: Preview input, Split output, Format conversion for output */
+ private static final int REQUEST_PIPELINE_MAX_DEPTH_OURS = 3;
+ /* TODO: Update above maxDepth values once we do more performance measurements */
+
+ // For approximating JPEG stall durations
+ private static final long APPROXIMATE_CAPTURE_DELAY_MS = 200; // 200 milliseconds
+ private static final long APPROXIMATE_SENSOR_AREA_PX = (1 << 23); // 8 megapixels
+ private static final long APPROXIMATE_JPEG_ENCODE_TIME_MS = 600; // 600 milliseconds
+
+ /*
+ * Development hijinks: Lie about not supporting certain capabilities
+ *
+ * - Unblock some CTS tests from running whose main intent is not the metadata itself
+ *
+ * TODO: Remove these constants and strip out any code that previously relied on them
+ * being set to true.
+ */
+ private static final boolean LIE_ABOUT_FLASH = true;
+ private static final boolean LIE_ABOUT_AE = true;
+ private static final boolean LIE_ABOUT_AF = true;
+ private static final boolean LIE_ABOUT_AWB = true;
+
+ /**
+ * Create characteristics for a legacy device by mapping the {@code parameters}
+ * and {@code info}
+ *
+ * @param parameters A non-{@code null} parameters set
+ * @param info Camera info with camera facing direction and angle of orientation
+ *
+ * @return static camera characteristics for a camera device
+ *
+ * @throws NullPointerException if any of the args were {@code null}
+ */
+ public static CameraCharacteristics createCharacteristics(Camera.Parameters parameters,
+ CameraInfo info) {
+ checkNotNull(parameters, "parameters must not be null");
+ checkNotNull(info, "info must not be null");
+
+ String paramStr = parameters.flatten();
+ android.hardware.CameraInfo outerInfo = new android.hardware.CameraInfo();
+ outerInfo.info = info;
+
+ return createCharacteristics(paramStr, outerInfo);
+ }
/**
* Create characteristics for a legacy device by mapping the {@code parameters}
@@ -74,11 +127,11 @@
CameraMetadataNative m = new CameraMetadataNative();
- mapCameraInfo(m, info.info);
+ mapCharacteristicsFromInfo(m, info.info);
Camera.Parameters params = Camera.getEmptyParameters();
params.unflatten(parameters);
- mapCameraParameters(m, params);
+ mapCharacteristicsFromParameters(m, params);
if (VERBOSE) {
Log.v(TAG, "createCharacteristics metadata:");
@@ -90,20 +143,51 @@
return new CameraCharacteristics(m);
}
- private static void mapCameraInfo(CameraMetadataNative m, CameraInfo i) {
+ private static void mapCharacteristicsFromInfo(CameraMetadataNative m, CameraInfo i) {
m.set(LENS_FACING, i.facing == CameraInfo.CAMERA_FACING_BACK ?
LENS_FACING_BACK : LENS_FACING_FRONT);
m.set(SENSOR_ORIENTATION, i.orientation);
}
- private static void mapCameraParameters(CameraMetadataNative m, Camera.Parameters p) {
+ private static void mapCharacteristicsFromParameters(CameraMetadataNative m,
+ Camera.Parameters p) {
+ /*
+ * info.supportedHardwareLevel
+ */
m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED);
mapStreamConfigs(m, p);
- mapAeConfig(m, p);
- mapCapabilities(m, p);
+ /*
+ * control.ae*
+ */
+ mapControlAe(m, p);
+ /*
+ * control.awb*
+ */
+ mapControlAwb(m, p);
+ /*
+ * control.*
+ * - Anything that doesn't have a set of related fields
+ */
+ mapControlOther(m, p);
+ /*
+ * lens.*
+ */
mapLens(m, p);
+ /*
+ * flash.*
+ */
mapFlash(m, p);
+
+ /*
+ * request.*
+ */
+ mapRequest(m, p);
// TODO: map other fields
+
+ /*
+ * sync.*
+ */
+ mapSync(m, p);
}
private static void mapStreamConfigs(CameraMetadataNative m, Camera.Parameters p) {
@@ -139,9 +223,15 @@
List<Camera.Size> jpegSizes = p.getSupportedPictureSizes();
appendStreamConfig(availableStreamConfigs,
HAL_PIXEL_FORMAT_BLOB, p.getSupportedPictureSizes());
+ /*
+ * scaler.availableStreamConfigurations
+ */
m.set(SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
availableStreamConfigs.toArray(new StreamConfiguration[0]));
+ /*
+ * scaler.availableMinFrameDurations
+ */
// No frame durations available
m.set(SCALER_AVAILABLE_MIN_FRAME_DURATIONS, new StreamConfigurationDuration[0]);
@@ -157,15 +247,23 @@
longestStallDuration = stallDuration;
}
}
+ /*
+ * scaler.availableStallDurations
+ */
// Set stall durations for jpeg, other formats use default stall duration
m.set(SCALER_AVAILABLE_STALL_DURATIONS, jpegStalls);
+ /*
+ * sensor.info.maxFrameDuration
+ */
m.set(SENSOR_INFO_MAX_FRAME_DURATION, longestStallDuration);
}
@SuppressWarnings({"unchecked"})
- private static void mapAeConfig(CameraMetadataNative m, Camera.Parameters p) {
-
+ private static void mapControlAe(CameraMetadataNative m, Camera.Parameters p) {
+ /*
+ * control.aeAvailableTargetFpsRanges
+ */
List<int[]> fpsRanges = p.getSupportedPreviewFpsRange();
if (fpsRanges == null) {
throw new AssertionError("Supported FPS ranges cannot be null.");
@@ -182,6 +280,10 @@
}
m.set(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, ranges);
+ /*
+ * control.aeAvailableAntiBandingModes
+ */
+
List<String> antiBandingModes = p.getSupportedAntibanding();
int antiBandingModesSize = antiBandingModes.size();
if (antiBandingModesSize > 0) {
@@ -198,11 +300,61 @@
}
m.set(CONTROL_AE_AVAILABLE_ANTIBANDING_MODES, Arrays.copyOf(modes, j));
}
+
+ /*
+ * control.aeAvailableModes
+ */
+ List<String> flashModes = p.getSupportedFlashModes();
+
+ String[] flashModeStrings = new String[] {
+ Camera.Parameters.FLASH_MODE_AUTO,
+ Camera.Parameters.FLASH_MODE_ON,
+ Camera.Parameters.FLASH_MODE_RED_EYE,
+ // Map these manually
+ Camera.Parameters.FLASH_MODE_TORCH,
+ Camera.Parameters.FLASH_MODE_OFF,
+ };
+ int[] flashModeInts = new int[] {
+ CONTROL_AE_MODE_ON,
+ CONTROL_AE_MODE_ON_AUTO_FLASH,
+ CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
+ };
+ int[] aeAvail = convertStringListToIntArray(flashModes, flashModeStrings, flashModeInts);
+
+ // No flash control -> AE is always on
+ if (aeAvail == null || aeAvail.length == 0) {
+ aeAvail = new int[] {
+ CONTROL_AE_MODE_ON
+ };
+ }
+
+ if (LIE_ABOUT_FLASH) {
+ // TODO: Remove this branch
+ Log.w(TAG, "mapControlAe - lying; saying we only support CONTROL_AE_MODE_ON");
+ aeAvail = new int[] {
+ CONTROL_AE_MODE_ON
+ };
+ }
+
+ m.set(CONTROL_AE_AVAILABLE_MODES, aeAvail);
}
- private static void mapCapabilities(CameraMetadataNative m, Camera.Parameters p) {
- int[] capabilities = { REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE };
- m.set(REQUEST_AVAILABLE_CAPABILITIES, capabilities);
+ private static void mapControlAwb(CameraMetadataNative m, Camera.Parameters p) {
+ if (!LIE_ABOUT_AWB) {
+ throw new AssertionError("Not implemented yet");
+ }
+ }
+
+ private static void mapControlOther(CameraMetadataNative m, Camera.Parameters p) {
+ /*
+ * android.control.maxRegions
+ */
+ int[] maxRegions = new int[3];
+ maxRegions[0] = p.getMaxNumMeteringAreas();
+ maxRegions[1] = 0; // AWB regions not supported in API1
+ maxRegions[2] = p.getMaxNumFocusAreas();
+ m.set(CONTROL_MAX_REGIONS, maxRegions);
+ // TODO
}
private static void mapLens(CameraMetadataNative m, Camera.Parameters p) {
@@ -211,6 +363,9 @@
* but if it's not, we can't tell the minimum focus distance, so leave it null then.
*/
if (p.getFocusMode() == Camera.Parameters.FOCUS_MODE_FIXED) {
+ /*
+ * lens.info.minimumFocusDistance
+ */
m.set(LENS_INFO_MINIMUM_FOCUS_DISTANCE, LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS);
}
}
@@ -226,9 +381,57 @@
}
}
+ if (LIE_ABOUT_FLASH && flashAvailable) {
+ // TODO: remove this branch
+ Log.w(TAG, "mapFlash - lying; saying we never support flash");
+ flashAvailable = false;
+ }
+
+ /*
+ * flash.info.available
+ */
m.set(FLASH_INFO_AVAILABLE, flashAvailable);
}
+ private static void mapRequest(CameraMetadataNative m, Parameters p) {
+ /*
+ * request.availableCapabilities
+ */
+ int[] capabilities = { REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE };
+ m.set(REQUEST_AVAILABLE_CAPABILITIES, capabilities);
+
+ /*
+ * request.maxNumOutputStreams
+ */
+ int[] outputStreams = {
+ /* RAW */
+ REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_RAW,
+ /* Processed & Not-Stalling */
+ REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC,
+ /* Processed & Stalling */
+ REQUEST_MAX_NUM_OUTPUT_STREAMS_COUNT_PROC_STALL,
+ };
+ m.set(REQUEST_MAX_NUM_OUTPUT_STREAMS, outputStreams);
+
+ /*
+ * request.maxNumInputStreams
+ */
+ m.set(REQUEST_MAX_NUM_INPUT_STREAMS, REQUEST_MAX_NUM_INPUT_STREAMS_COUNT);
+
+ /*
+ * request.pipelineMaxDepth
+ */
+ m.set(REQUEST_PIPELINE_MAX_DEPTH,
+ (byte)(REQUEST_PIPELINE_MAX_DEPTH_HAL1 + REQUEST_PIPELINE_MAX_DEPTH_OURS));
+ }
+
+ private static void mapSync(CameraMetadataNative m, Parameters p) {
+ /*
+ * sync.maxLatency
+ */
+ m.set(SYNC_MAX_LATENCY, SYNC_MAX_LATENCY_UNKNOWN);
+ }
+
private static void appendStreamConfig(
ArrayList<StreamConfiguration> configs, int format, List<Camera.Size> sizes) {
for (Camera.Size size : sizes) {
@@ -303,8 +506,8 @@
private static long calculateJpegStallDuration(Camera.Size size) {
long baseDuration = APPROXIMATE_CAPTURE_DELAY_MS * NS_PER_MS; // 200ms for capture
long area = size.width * (long) size.height;
- long stallPerArea = APPROXIMATE_JPEG_ENCODE_TIME * NS_PER_MS /
- APPROXIMATE_SENSOR_AREA; // 600ms stall for 8mp
+ long stallPerArea = APPROXIMATE_JPEG_ENCODE_TIME_MS * NS_PER_MS /
+ APPROXIMATE_SENSOR_AREA_PX; // 600ms stall for 8mp
return baseDuration + area * stallPerArea;
}
@@ -320,7 +523,52 @@
CaptureRequest request,
long timestamp) {
CameraMetadataNative result = new CameraMetadataNative();
+
+ /*
+ * control
+ */
+ // control.afState
+ if (LIE_ABOUT_AF) {
+ // TODO: Implement autofocus state machine
+ result.set(CaptureResult.CONTROL_AF_MODE, request.get(CaptureRequest.CONTROL_AF_MODE));
+ }
+
+ // control.aeState
+ if (LIE_ABOUT_AE) {
+ // Lie to pass CTS temporarily.
+ // TODO: Implement precapture trigger, after which we can report CONVERGED ourselves
+ result.set(CaptureResult.CONTROL_AE_STATE,
+ CONTROL_AE_STATE_CONVERGED);
+
+ result.set(CaptureResult.CONTROL_AE_MODE,
+ request.get(CaptureRequest.CONTROL_AE_MODE));
+ }
+
+ // control.awbLock
+ result.set(CaptureResult.CONTROL_AWB_LOCK, params.getAutoWhiteBalanceLock());
+
+ // control.awbState
+ if (LIE_ABOUT_AWB) {
+ // Lie to pass CTS temporarily.
+ // TODO: CTS needs to be updated not to query this value
+ // for LIMITED devices unless its guaranteed to be available.
+ result.set(CaptureResult.CONTROL_AWB_STATE,
+ CameraMetadata.CONTROL_AWB_STATE_CONVERGED);
+ // TODO: Read the awb mode from parameters instead
+ result.set(CaptureResult.CONTROL_AWB_MODE,
+ request.get(CaptureRequest.CONTROL_AWB_MODE));
+ }
+
+ /*
+ * lens
+ */
+ // lens.focalLength
result.set(CaptureResult.LENS_FOCAL_LENGTH, params.getFocalLength());
+
+ /*
+ * sensor
+ */
+ // sensor.timestamp
result.set(CaptureResult.SENSOR_TIMESTAMP, timestamp);
// TODO: Remaining result metadata tags conversions.
@@ -335,17 +583,149 @@
*/
public static void convertRequestMetadata(CaptureRequest request,
/*out*/Camera.Parameters params) {
+
+ /*
+ * control.ae*
+ */
+ // control.aeAntibandingMode
Integer antiBandingMode = request.get(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE);
if (antiBandingMode != null) {
String legacyMode = convertAntiBandingModeToLegacy(antiBandingMode);
if (legacyMode != null) params.setAntibanding(legacyMode);
}
+ // control.aeTargetFpsRange
Range<Integer> aeFpsRange = request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
if (aeFpsRange != null) {
int[] legacyFps = convertAeFpsRangeToLegacy(aeFpsRange);
params.setPreviewFpsRange(legacyFps[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
legacyFps[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
}
+
+ /*
+ * control
+ */
+ // control.awbLock
+ Boolean awbLock = request.get(CaptureRequest.CONTROL_AWB_LOCK);
+ params.setAutoWhiteBalanceLock(awbLock == null ? false : awbLock);
+ }
+
+ /**
+ * Create an int[] from the List<> by using {@code convertFrom} and {@code convertTo}
+ * as a one-to-one map (via the index).
+ *
+ * <p>Strings not appearing in {@code convertFrom} are ignored (with a warning);
+ * strings appearing in {@code convertFrom} but not {@code convertTo} are silently
+ * dropped.</p>
+ *
+ * @param list Source list of strings
+ * @param convertFrom Conversion list of strings
+ * @param convertTo Conversion list of ints
+ * @return An array of ints where the values correspond to the ones in {@code convertTo}
+ * or {@code null} if {@code list} was {@code null}
+ */
+ private static int[] convertStringListToIntArray(
+ List<String> list, String[] convertFrom, int[] convertTo) {
+ if (list == null) {
+ return null;
+ }
+
+ List<Integer> convertedList = new ArrayList<>(list.size());
+
+ for (String str : list) {
+ int strIndex = getArrayIndex(convertFrom, str);
+
+ // Guard against bad API1 values
+ if (strIndex < 0) {
+ Log.w(TAG, "Ignoring invalid parameter " + str);
+ continue;
+ }
+
+ // Ignore values we can't map into (intentional)
+ if (strIndex < convertTo.length) {
+ convertedList.add(convertTo[strIndex]);
+ }
+ }
+
+ int[] returnArray = new int[convertedList.size()];
+ for (int i = 0; i < returnArray.length; ++i) {
+ returnArray[i] = convertedList.get(i);
+ }
+
+ return returnArray;
+ }
+
+ /** Return the index of {@code needle} in the {@code array}, or else {@code -1} */
+ private static <T> int getArrayIndex(T[] array, T needle) {
+ if (needle == null) {
+ return -1;
+ }
+
+ int index = 0;
+ for (T elem : array) {
+ if (Objects.equals(elem, needle)) {
+ return index;
+ }
+ index++;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Create a request template
+ *
+ * @param c a non-{@code null} camera characteristics for this camera
+ * @param templateId a non-negative template ID
+ *
+ * @return a non-{@code null} request template
+ *
+ * @throws IllegalArgumentException if {@code templateId} was invalid
+ *
+ * @see android.hardware.camera2.CameraDevice#TEMPLATE_MANUAL
+ */
+ public static CameraMetadataNative createRequestTemplate(
+ CameraCharacteristics c, int templateId) {
+ if (templateId < 0 || templateId > CameraDevice.TEMPLATE_MANUAL) {
+ throw new IllegalArgumentException("templateId out of range");
+ }
+
+ CameraMetadataNative m = new CameraMetadataNative();
+
+ /*
+ * NOTE: If adding new code here and it needs to query the static info,
+ * query the camera characteristics, so we can reuse this for api2 code later
+ * to create our own templates in the framework
+ */
+
+ if (LIE_ABOUT_AWB) {
+ m.set(CaptureRequest.CONTROL_AWB_MODE, CameraMetadata.CONTROL_AWB_MODE_AUTO);
+ } else {
+ throw new AssertionError("Valid control.awbMode not implemented yet");
+ }
+
+ // control.aeMode
+ m.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON);
+ // AE is always unconditionally available in API1 devices
+
+ // control.afMode
+ {
+ Float minimumFocusDistance = c.get(LENS_INFO_MINIMUM_FOCUS_DISTANCE);
+
+ int afMode;
+ if (minimumFocusDistance != null &&
+ minimumFocusDistance == LENS_INFO_MINIMUM_FOCUS_DISTANCE_FIXED_FOCUS) {
+ // Cannot control auto-focus with fixed-focus cameras
+ afMode = CameraMetadata.CONTROL_AF_MODE_OFF;
+ } else {
+ // If a minimum focus distance is reported; the camera must have AF
+ afMode = CameraMetadata.CONTROL_AF_MODE_AUTO;
+ }
+
+ m.set(CaptureRequest.CONTROL_AF_MODE, afMode);
+ }
+
+ // TODO: map other request template values
+ return m;
}
}
diff --git a/core/java/android/hardware/camera2/legacy/RequestHolder.java b/core/java/android/hardware/camera2/legacy/RequestHolder.java
index 8a9052f..e674736 100644
--- a/core/java/android/hardware/camera2/legacy/RequestHolder.java
+++ b/core/java/android/hardware/camera2/legacy/RequestHolder.java
@@ -18,6 +18,7 @@
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.impl.CameraMetadataNative;
+import android.util.Log;
import android.view.Surface;
import java.util.Collection;
@@ -26,6 +27,7 @@
* Immutable container for a single capture request and associated information.
*/
public class RequestHolder {
+ private static final String TAG = "RequestHolder";
private final boolean mRepeating;
private final CaptureRequest mRequest;
@@ -89,8 +91,12 @@
*/
public boolean hasJpegTargets() {
for (Surface s : getHolderTargets()) {
- if (jpegType(s)) {
- return true;
+ try {
+ if (jpegType(s)) {
+ return true;
+ }
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ Log.w(TAG, "Surface abandoned, skipping...", e);
}
}
return false;
@@ -102,8 +108,12 @@
*/
public boolean hasPreviewTargets() {
for (Surface s : getHolderTargets()) {
- if (previewType(s)) {
- return true;
+ try {
+ if (previewType(s)) {
+ return true;
+ }
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ Log.w(TAG, "Surface abandoned, skipping...", e);
}
}
return false;
@@ -115,8 +125,12 @@
*/
public Surface getFirstPreviewTarget() {
for (Surface s : getHolderTargets()) {
- if (previewType(s)) {
- return s;
+ try {
+ if (previewType(s)) {
+ return s;
+ }
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ Log.w(TAG, "Surface abandoned, skipping...", e);
}
}
return null;
@@ -128,8 +142,9 @@
* @param s a {@link Surface} to check.
* @return true if the surface requires a jpeg buffer.
*/
- public static boolean jpegType(Surface s) {
- if (LegacyCameraDevice.nativeDetectSurfaceType(s) ==
+ public static boolean jpegType(Surface s)
+ throws LegacyExceptionUtils.BufferQueueAbandonedException {
+ if (LegacyCameraDevice.detectSurfaceType(s) ==
CameraMetadataNative.NATIVE_JPEG_FORMAT) {
return true;
}
@@ -149,8 +164,9 @@
* @param s a {@link Surface} to check.
* @return true if the surface requires a non-jpeg buffer type.
*/
- public static boolean previewType(Surface s) {
- if (LegacyCameraDevice.nativeDetectSurfaceType(s) !=
+ public static boolean previewType(Surface s)
+ throws LegacyExceptionUtils.BufferQueueAbandonedException {
+ if (LegacyCameraDevice.detectSurfaceType(s) !=
CameraMetadataNative.NATIVE_JPEG_FORMAT) {
return true;
}
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index a4b1099..73b84656 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -64,7 +64,7 @@
private static final int MSG_CLEANUP = 3;
private static final int PREVIEW_FRAME_TIMEOUT = 300; // ms
- private static final int JPEG_FRAME_TIMEOUT = 1000; // ms
+ private static final int JPEG_FRAME_TIMEOUT = 3000; // ms (same as CTS for API2)
private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
private boolean mPreviewRunning = false;
@@ -105,11 +105,11 @@
/**
- * Comparator for {@link Size} objects.
+ * Comparator for {@link Size} objects by the area.
*
- * <p>This comparator compares by rectangle area. Tiebreaks on width.</p>
+ * <p>This comparator totally orders by rectangle area. Tiebreaks on width.</p>
*/
- private static class SizeComparator implements Comparator<Size> {
+ private static class SizeAreaComparator implements Comparator<Size> {
@Override
public int compare(Size size, Size size2) {
if (size == null || size2 == null) {
@@ -201,11 +201,15 @@
return;
}
for (Surface s : holder.getHolderTargets()) {
- if (RequestHolder.jpegType(s)) {
- Log.i(TAG, "Producing jpeg buffer...");
- LegacyCameraDevice.nativeSetSurfaceDimens(s, data.length, /*height*/1);
- LegacyCameraDevice.nativeProduceFrame(s, data, data.length, /*height*/1,
- CameraMetadataNative.NATIVE_JPEG_FORMAT);
+ try {
+ if (RequestHolder.jpegType(s)) {
+ Log.i(TAG, "Producing jpeg buffer...");
+ LegacyCameraDevice.setSurfaceDimens(s, data.length, /*height*/1);
+ LegacyCameraDevice.produceFrame(s, data, data.length, /*height*/1,
+ CameraMetadataNative.NATIVE_JPEG_FORMAT);
+ }
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ Log.w(TAG, "Surface abandoned, dropping frame. ", e);
}
}
mReceivedJpeg.open();
@@ -262,7 +266,11 @@
}
private void doJpegCapture(RequestHolder request) throws IOException {
+ if (DEBUG) Log.d(TAG, "doJpegCapture");
+
if (!mPreviewRunning) {
+ if (DEBUG) Log.d(TAG, "doJpegCapture - create fake surface");
+
createDummySurface();
mCamera.setPreviewTexture(mDummyTexture);
startPreview();
@@ -319,14 +327,18 @@
if (outputs != null) {
for (Surface s : outputs) {
- int format = LegacyCameraDevice.nativeDetectSurfaceType(s);
- switch (format) {
- case CameraMetadataNative.NATIVE_JPEG_FORMAT:
- mCallbackOutputs.add(s);
- break;
- default:
- mPreviewOutputs.add(s);
- break;
+ try {
+ int format = LegacyCameraDevice.detectSurfaceType(s);
+ switch (format) {
+ case CameraMetadataNative.NATIVE_JPEG_FORMAT:
+ mCallbackOutputs.add(s);
+ break;
+ default:
+ mPreviewOutputs.add(s);
+ break;
+ }
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ Log.w(TAG, "Surface abandoned, skipping...", e);
}
}
}
@@ -334,9 +346,12 @@
if (mPreviewOutputs.size() > 0) {
List<Size> outputSizes = new ArrayList<>(outputs.size());
for (Surface s : mPreviewOutputs) {
- int[] dimens = {0, 0};
- LegacyCameraDevice.nativeDetectSurfaceDimens(s, dimens);
- outputSizes.add(new Size(dimens[0], dimens[1]));
+ try {
+ Size size = LegacyCameraDevice.getSurfaceSize(s);
+ outputSizes.add(size);
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ Log.w(TAG, "Surface abandoned, skipping...", e);
+ }
}
Size largestOutput = findLargestByArea(outputSizes);
@@ -373,6 +388,18 @@
}
}
+ Size smallestSupportedJpegSize = calculatePictureSize(mCallbackOutputs, mParams);
+ if (smallestSupportedJpegSize != null) {
+ /*
+ * Set takePicture size to the smallest supported JPEG size large enough
+ * to scale/crop out of for the bounding rectangle of the configured JPEG sizes.
+ */
+
+ Log.i(TAG, "configureOutputs - set take picture size to " + smallestSupportedJpegSize);
+ mParams.setPictureSize(
+ smallestSupportedJpegSize.getWidth(), smallestSupportedJpegSize.getHeight());
+ }
+
// TODO: Detect and optimize single-output paths here to skip stream teeing.
if (mGLThreadManager == null) {
mGLThreadManager = new GLThreadManager(mCameraId);
@@ -385,10 +412,109 @@
if (mPreviewTexture != null) {
mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback);
}
+
+ // TODO: configure the JPEG surface with some arbitrary size
+ // using LegacyCameraDevice.nativeConfigureSurface
+ }
+
+ /**
+ * Find a JPEG size (that is supported by the legacy camera device) which is equal to or larger
+ * than all of the configured {@code JPEG} outputs (by both width and height).
+ *
+ * <p>If multiple supported JPEG sizes are larger, select the smallest of them which
+ * still satisfies the above constraint.</p>
+ *
+ * <p>As a result, the returned size is guaranteed to be usable without needing
+ * to upscale any of the outputs. If only one {@code JPEG} surface is used,
+ * then no scaling/cropping is necessary between the taken picture and
+ * the {@code JPEG} output surface.</p>
+ *
+ * @param callbackOutputs a non-{@code null} list of {@code Surface}s with any image formats
+ * @param params api1 parameters (used for reading only)
+ *
+ * @return a size large enough to fit all of the configured {@code JPEG} outputs, or
+ * {@code null} if the {@code callbackOutputs} did not have any {@code JPEG}
+ * surfaces.
+ */
+ private Size calculatePictureSize(
+ Collection<Surface> callbackOutputs, Camera.Parameters params) {
+ /*
+ * Find the largest JPEG size (if any), from the configured outputs:
+ * - the api1 picture size should be set to the smallest legal size that's at least as large
+ * as the largest configured JPEG size
+ */
+ List<Size> configuredJpegSizes = new ArrayList<Size>();
+ for (Surface callbackSurface : callbackOutputs) {
+ try {
+ int format = LegacyCameraDevice.detectSurfaceType(callbackSurface);
+
+ if (format != CameraMetadataNative.NATIVE_JPEG_FORMAT) {
+ continue; // Ignore non-JPEG callback formats
+ }
+
+ Size jpegSize = LegacyCameraDevice.getSurfaceSize(callbackSurface);
+ configuredJpegSizes.add(jpegSize);
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ Log.w(TAG, "Surface abandoned, skipping...", e);
+ }
+ }
+ if (!configuredJpegSizes.isEmpty()) {
+ /*
+ * Find the largest configured JPEG width, and height, independently
+ * of the rest.
+ *
+ * The rest of the JPEG streams can be cropped out of this smallest bounding
+ * rectangle.
+ */
+ int maxConfiguredJpegWidth = -1;
+ int maxConfiguredJpegHeight = -1;
+ for (Size jpegSize : configuredJpegSizes) {
+ maxConfiguredJpegWidth = jpegSize.getWidth() > maxConfiguredJpegWidth ?
+ jpegSize.getWidth() : maxConfiguredJpegWidth;
+ maxConfiguredJpegHeight = jpegSize.getHeight() > maxConfiguredJpegHeight ?
+ jpegSize.getHeight() : maxConfiguredJpegHeight;
+ }
+ Size smallestBoundJpegSize = new Size(maxConfiguredJpegWidth, maxConfiguredJpegHeight);
+
+ List<Size> supportedJpegSizes = convertSizeList(params.getSupportedPictureSizes());
+
+ /*
+ * Find the smallest supported JPEG size that can fit the smallest bounding
+ * rectangle for the configured JPEG sizes.
+ */
+ List<Size> candidateSupportedJpegSizes = new ArrayList<>();
+ for (Size supportedJpegSize : supportedJpegSizes) {
+ if (supportedJpegSize.getWidth() >= maxConfiguredJpegWidth &&
+ supportedJpegSize.getHeight() >= maxConfiguredJpegHeight) {
+ candidateSupportedJpegSizes.add(supportedJpegSize);
+ }
+ }
+
+ if (candidateSupportedJpegSizes.isEmpty()) {
+ throw new AssertionError(
+ "Could not find any supported JPEG sizes large enough to fit " +
+ smallestBoundJpegSize);
+ }
+
+ Size smallestSupportedJpegSize = Collections.min(candidateSupportedJpegSizes,
+ new SizeAreaComparator());
+
+ if (!smallestSupportedJpegSize.equals(smallestBoundJpegSize)) {
+ Log.w(TAG,
+ String.format(
+ "configureOutputs - Will need to crop picture %s into "
+ + "smallest bound size %s",
+ smallestSupportedJpegSize, smallestBoundJpegSize));
+ }
+
+ return smallestSupportedJpegSize;
+ }
+
+ return null;
}
private static Size findLargestByArea(List<Size> sizes) {
- return Collections.max(sizes, new SizeComparator());
+ return Collections.max(sizes, new SizeAreaComparator());
}
private static boolean checkAspectRatiosMatch(Size a, Size b) {
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index bbc7005..e38624a 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -26,6 +26,7 @@
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.util.Log;
+import android.util.Size;
import android.view.Surface;
import java.nio.ByteBuffer;
@@ -338,22 +339,25 @@
}
int maxLength = 0;
- int[] dimens = new int[2];
for (EGLSurfaceHolder holder : surfaces) {
- LegacyCameraDevice.nativeDetectSurfaceDimens(holder.surface, dimens);
- int length = dimens[0] * dimens[1];
- // Find max surface size, ensure PBuffer can hold this many pixels
- maxLength = (length > maxLength) ? length : maxLength;
- int[] surfaceAttribs = {
- EGL14.EGL_WIDTH, dimens[0],
- EGL14.EGL_HEIGHT, dimens[1],
- EGL14.EGL_NONE
- };
- holder.width = dimens[0];
- holder.height = dimens[1];
- holder.eglSurface =
- EGL14.eglCreatePbufferSurface(mEGLDisplay, mConfigs, surfaceAttribs, 0);
- checkEglError("eglCreatePbufferSurface");
+ try {
+ Size size = LegacyCameraDevice.getSurfaceSize(holder.surface);
+ int length = size.getWidth() * size.getHeight();
+ // Find max surface size, ensure PBuffer can hold this many pixels
+ maxLength = (length > maxLength) ? length : maxLength;
+ int[] surfaceAttribs = {
+ EGL14.EGL_WIDTH, size.getWidth(),
+ EGL14.EGL_HEIGHT, size.getHeight(),
+ EGL14.EGL_NONE
+ };
+ holder.width = size.getWidth();
+ holder.height = size.getHeight();
+ holder.eglSurface =
+ EGL14.eglCreatePbufferSurface(mEGLDisplay, mConfigs, surfaceAttribs, 0);
+ checkEglError("eglCreatePbufferSurface");
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ Log.w(TAG, "Surface abandoned, skipping...", e);
+ }
}
mPBufferPixels = ByteBuffer.allocateDirect(maxLength * PBUFFER_PIXEL_BYTES)
.order(ByteOrder.nativeOrder());
@@ -438,15 +442,19 @@
for (Surface s : surfaces) {
// If pixel conversions aren't handled by egl, use a pbuffer
- if (LegacyCameraDevice.needsConversion(s)) {
- LegacyCameraDevice.nativeSetSurfaceFormat(s, ImageFormat.YV12);
- EGLSurfaceHolder holder = new EGLSurfaceHolder();
- holder.surface = s;
- mConversionSurfaces.add(holder);
- } else {
- EGLSurfaceHolder holder = new EGLSurfaceHolder();
- holder.surface = s;
- mSurfaces.add(holder);
+ try {
+ if (LegacyCameraDevice.needsConversion(s)) {
+ LegacyCameraDevice.setSurfaceFormat(s, ImageFormat.YV12);
+ EGLSurfaceHolder holder = new EGLSurfaceHolder();
+ holder.surface = s;
+ mConversionSurfaces.add(holder);
+ } else {
+ EGLSurfaceHolder holder = new EGLSurfaceHolder();
+ holder.surface = s;
+ mSurfaces.add(holder);
+ }
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ Log.w(TAG, "Surface abandoned, skipping configuration... ", e);
}
}
@@ -503,10 +511,14 @@
GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height, GLES20.GL_RGBA,
GLES20.GL_UNSIGNED_BYTE, mPBufferPixels);
checkGlError("glReadPixels");
- int format = LegacyCameraDevice.nativeDetectSurfaceType(holder.surface);
- LegacyCameraDevice.nativeProduceFrame(holder.surface, mPBufferPixels.array(),
- holder.width, holder.height, format);
- swapBuffers(holder.eglSurface);
+ try {
+ int format = LegacyCameraDevice.detectSurfaceType(holder.surface);
+ LegacyCameraDevice.produceFrame(holder.surface, mPBufferPixels.array(),
+ holder.width, holder.height, format);
+ swapBuffers(holder.eglSurface);
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ Log.w(TAG, "Surface abandoned, dropping frame. ", e);
+ }
}
}
}
diff --git a/core/java/android/hardware/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java
index d86dd5e..c87b674 100644
--- a/core/java/android/hardware/hdmi/HdmiCec.java
+++ b/core/java/android/hardware/hdmi/HdmiCec.java
@@ -101,6 +101,9 @@
/** Logical address used to indicate it is not initialized or invalid. */
public static final int ADDR_INVALID = -1;
+ /** Logical address used to indicate the source comes from internal device. */
+ public static final int ADDR_INTERNAL = 0xFFFF;
+
// TODO: Complete the list of CEC messages definition.
public static final int MESSAGE_FEATURE_ABORT = 0x00;
public static final int MESSAGE_IMAGE_VIEW_ON = 0x04;
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index b68ce36..396efff 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2372,12 +2372,10 @@
*/
public static boolean setProcessDefaultNetwork(Network network) {
if (network == null) {
- NetworkUtils.unbindProcessToNetwork();
+ return NetworkUtils.unbindProcessToNetwork();
} else {
- NetworkUtils.bindProcessToNetwork(network.netId);
+ return NetworkUtils.bindProcessToNetwork(network.netId);
}
- // TODO fix return value
- return true;
}
/**
@@ -2404,11 +2402,9 @@
*/
public static boolean setProcessDefaultNetworkForHostResolution(Network network) {
if (network == null) {
- NetworkUtils.unbindProcessToNetworkForHostResolution();
+ return NetworkUtils.unbindProcessToNetworkForHostResolution();
} else {
- NetworkUtils.bindProcessToNetworkForHostResolution(network.netId);
+ return NetworkUtils.bindProcessToNetworkForHostResolution(network.netId);
}
- // TODO hook up the return value.
- return true;
}
}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index e7184ed..8be5cf8 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -264,13 +264,32 @@
}
/**
- * Adds the given {@link InetAddress} to the list of DNS servers.
+ * Adds the given {@link InetAddress} to the list of DNS servers, if not present.
*
* @param dnsServer The {@link InetAddress} to add to the list of DNS servers.
+ * @return true if the DNS server was added, false if it was already present.
* @hide
*/
- public void addDnsServer(InetAddress dnsServer) {
- if (dnsServer != null) mDnses.add(dnsServer);
+ public boolean addDnsServer(InetAddress dnsServer) {
+ if (dnsServer != null && !mDnses.contains(dnsServer)) {
+ mDnses.add(dnsServer);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Replaces the DNS servers in this {@code LinkProperties} with
+ * the given {@link Collection} of {@link InetAddress} objects.
+ *
+ * @param addresses The {@link Collection} of DNS servers to set in this object.
+ * @hide
+ */
+ public void setDnsServers(Collection<InetAddress> dnsServers) {
+ mDnses.clear();
+ for (InetAddress dnsServer: dnsServers) {
+ addDnsServer(dnsServer);
+ }
}
/**
@@ -540,14 +559,14 @@
}
/**
- * Returns true if this link has an IPv6 address.
+ * Returns true if this link has a global preferred IPv6 address.
*
- * @return {@code true} if there is an IPv6 address, {@code false} otherwise.
+ * @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise.
* @hide
*/
- public boolean hasIPv6Address() {
+ public boolean hasGlobalIPv6Address() {
for (LinkAddress address : mLinkAddresses) {
- if (address.getAddress() instanceof Inet6Address) {
+ if (address.getAddress() instanceof Inet6Address && address.isGlobalPreferred()) {
return true;
}
}
@@ -555,6 +574,80 @@
}
/**
+ * Returns true if this link has an IPv4 default route.
+ *
+ * @return {@code true} if there is an IPv4 default route, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasIPv4DefaultRoute() {
+ for (RouteInfo r : mRoutes) {
+ if (r.isIPv4Default()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this link has an IPv6 default route.
+ *
+ * @return {@code true} if there is an IPv6 default route, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasIPv6DefaultRoute() {
+ for (RouteInfo r : mRoutes) {
+ if (r.isIPv6Default()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this link has an IPv4 DNS server.
+ *
+ * @return {@code true} if there is an IPv4 DNS server, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasIPv4DnsServer() {
+ for (InetAddress ia : mDnses) {
+ if (ia instanceof Inet4Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this link has an IPv6 DNS server.
+ *
+ * @return {@code true} if there is an IPv6 DNS server, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasIPv6DnsServer() {
+ for (InetAddress ia : mDnses) {
+ if (ia instanceof Inet6Address) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this link is provisioned for global connectivity. For IPv6, this requires an
+ * IP address, default route, and DNS server. For IPv4, this requires only an IPv4 address,
+ * because WifiStateMachine accepts static configurations that only specify an address but not
+ * DNS servers or a default route.
+ *
+ * @return {@code true} if the link is provisioned, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isProvisioned() {
+ return (hasIPv4Address() ||
+ (hasGlobalIPv6Address() && hasIPv6DefaultRoute() && hasIPv6DnsServer()));
+ }
+
+ /**
* Compares this {@code LinkProperties} interface name against the target
*
* @param target LinkProperties to compare.
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 0a422c6..9a22d78 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -24,6 +24,7 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
+import java.net.SocketException;
import java.net.UnknownHostException;
import javax.net.SocketFactory;
@@ -148,7 +149,9 @@
// Query a property of the underlying socket to ensure the underlying
// socket exists so a file descriptor is available to bind to a network.
socket.getReuseAddress();
- NetworkUtils.bindSocketToNetwork(socket.getFileDescriptor$().getInt$(), mNetId);
+ if (!NetworkUtils.bindSocketToNetwork(socket.getFileDescriptor$().getInt$(), mNetId)) {
+ throw new SocketException("Failed to bind socket to network.");
+ }
return socket;
}
}
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 352512e..1a9a6378 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -20,6 +20,7 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
import android.content.Intent;
+import android.net.NetworkScorerAppManager.NetworkScorerAppData;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -38,7 +39,9 @@
* <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission.
* <li>Includes a receiver for {@link #ACTION_SCORE_NETWORKS} guarded by the
* {@link android.Manifest.permission#BROADCAST_SCORE_NETWORKS} permission which scores networks
- * and (eventually) calls {@link #updateScores} with the results.
+ * and (eventually) calls {@link #updateScores} with the results. If this receiver specifies an
+ * android:label attribute, this label will be used when referring to the application throughout
+ * system settings; otherwise, the application label will be used.
* </ul>
*
* <p>The system keeps track of an active scorer application; at any time, only this application
@@ -105,7 +108,11 @@
* scorer.
*/
public String getActiveScorerPackage() {
- return NetworkScorerAppManager.getActiveScorer(mContext);
+ NetworkScorerAppData app = NetworkScorerAppManager.getActiveScorer(mContext);
+ if (app == null) {
+ return null;
+ }
+ return app.mPackageName;
}
/**
diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java
index 3660e7a..7c61710 100644
--- a/core/java/android/net/NetworkScorerAppManager.java
+++ b/core/java/android/net/NetworkScorerAppManager.java
@@ -24,7 +24,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.provider.Settings;
-import android.provider.Settings.Global;
import android.text.TextUtils;
import android.util.Log;
@@ -35,6 +34,8 @@
/**
* Internal class for managing the primary network scorer application.
*
+ * TODO: Rename this to something more generic.
+ *
* @hide
*/
public final class NetworkScorerAppManager {
@@ -46,8 +47,21 @@
/** This class cannot be instantiated. */
private NetworkScorerAppManager() {}
+ public static class NetworkScorerAppData {
+ /** Package name of this scorer app. */
+ public final String mPackageName;
+
+ /** Name of this scorer app for display. */
+ public final CharSequence mScorerName;
+
+ public NetworkScorerAppData(String packageName, CharSequence scorerName) {
+ mScorerName = scorerName;
+ mPackageName = packageName;
+ }
+ }
+
/**
- * Returns the list of available scorer app package names.
+ * Returns the list of available scorer apps.
*
* <p>A network scorer is any application which:
* <ul>
@@ -58,8 +72,8 @@
*
* @return the list of scorers, or the empty list if there are no valid scorers.
*/
- public static Collection<String> getAllValidScorers(Context context) {
- List<String> scorers = new ArrayList<>();
+ public static Collection<NetworkScorerAppData> getAllValidScorers(Context context) {
+ List<NetworkScorerAppData> scorers = new ArrayList<>();
PackageManager pm = context.getPackageManager();
List<ResolveInfo> receivers = pm.queryBroadcastReceivers(SCORE_INTENT, 0 /* flags */);
@@ -81,33 +95,32 @@
// approved it as a network scorer.
continue;
}
- scorers.add(receiverInfo.packageName);
+ // NOTE: loadLabel will attempt to load the receiver's label and fall back to the app
+ // label if none is present.
+ scorers.add(new NetworkScorerAppData(
+ receiverInfo.packageName, receiverInfo.loadLabel(pm)));
}
return scorers;
}
/**
- * Get the application package name to use for scoring networks.
+ * Get the application to use for scoring networks.
*
- * @return the scorer package or null if scoring is disabled (including if no scorer was ever
+ * @return the scorer app info or null if scoring is disabled (including if no scorer was ever
* selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because
* it was disabled or uninstalled).
*/
- public static String getActiveScorer(Context context) {
+ public static NetworkScorerAppData getActiveScorer(Context context) {
String scorerPackage = Settings.Global.getString(context.getContentResolver(),
- Global.NETWORK_SCORER_APP);
- if (isPackageValidScorer(context, scorerPackage)) {
- return scorerPackage;
- } else {
- return null;
- }
+ Settings.Global.NETWORK_SCORER_APP);
+ return getScorer(context, scorerPackage);
}
/**
* Set the specified package as the default scorer application.
*
- * <p>The caller must have permission to write to {@link Settings.Global}.
+ * <p>The caller must have permission to write to {@link android.provider.Settings.Global}.
*
* @param context the context of the calling application
* @param packageName the packageName of the new scorer to use. If null, scoring will be
@@ -125,12 +138,12 @@
Log.i(TAG, "Changing network scorer from " + oldPackageName + " to " + packageName);
if (packageName == null) {
- Settings.Global.putString(context.getContentResolver(), Global.NETWORK_SCORER_APP,
- null);
+ Settings.Global.putString(context.getContentResolver(),
+ Settings.Global.NETWORK_SCORER_APP, null);
return true;
} else {
// We only make the change if the new package is valid.
- if (isPackageValidScorer(context, packageName)) {
+ if (getScorer(context, packageName) != null) {
Settings.Global.putString(context.getContentResolver(),
Settings.Global.NETWORK_SCORER_APP, packageName);
return true;
@@ -143,22 +156,30 @@
/** Determine whether the application with the given UID is the enabled scorer. */
public static boolean isCallerActiveScorer(Context context, int callingUid) {
- String defaultApp = getActiveScorer(context);
+ NetworkScorerAppData defaultApp = getActiveScorer(context);
if (defaultApp == null) {
return false;
}
AppOpsManager appOpsMgr = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
try {
- appOpsMgr.checkPackage(callingUid, defaultApp);
+ appOpsMgr.checkPackage(callingUid, defaultApp.mPackageName);
return true;
} catch (SecurityException e) {
return false;
}
}
- /** Returns true if the given package is a valid scorer. */
- public static boolean isPackageValidScorer(Context context, String packageName) {
- Collection<String> applications = getAllValidScorers(context);
- return packageName != null && applications.contains(packageName);
+ /** Returns the {@link NetworkScorerAppData} for the given app, or null if it's not a scorer. */
+ public static NetworkScorerAppData getScorer(Context context, String packageName) {
+ if (TextUtils.isEmpty(packageName)) {
+ return null;
+ }
+ Collection<NetworkScorerAppData> applications = getAllValidScorers(context);
+ for (NetworkScorerAppData app : applications) {
+ if (packageName.equals(app.mPackageName)) {
+ return app;
+ }
+ }
+ return null;
}
}
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 15c0a71..c4b17b6 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -118,13 +118,13 @@
* is by design so an application doesn't accidentally use sockets it thinks are still bound to
* a particular {@code Network}.
*/
- public native static void bindProcessToNetwork(int netId);
+ public native static boolean bindProcessToNetwork(int netId);
/**
* Clear any process specific {@code Network} binding. This reverts a call to
* {@link #bindProcessToNetwork}.
*/
- public native static void unbindProcessToNetwork();
+ public native static boolean unbindProcessToNetwork();
/**
* Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
@@ -138,7 +138,7 @@
*
* @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
*/
- public native static void bindProcessToNetworkForHostResolution(int netId);
+ public native static boolean bindProcessToNetworkForHostResolution(int netId);
/**
* Clears any process specific {@link Network} binding for host resolution. This does
@@ -146,13 +146,13 @@
*
* @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
*/
- public native static void unbindProcessToNetworkForHostResolution();
+ public native static boolean unbindProcessToNetworkForHostResolution();
/**
* Explicitly binds {@code socketfd} to the network designated by {@code netId}. This
* overrides any binding via {@link #bindProcessToNetwork}.
*/
- public native static void bindSocketToNetwork(int socketfd, int netId);
+ public native static boolean bindSocketToNetwork(int socketfd, int netId);
/**
* Convert a IPv4 address from an integer to an InetAddress.
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 63d6cd3..129248c 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -63,7 +63,6 @@
*/
private final String mInterface;
- private final boolean mIsDefault;
private final boolean mIsHost;
private final boolean mHasGateway;
@@ -128,7 +127,6 @@
}
mGateway = gateway;
mInterface = iface;
- mIsDefault = isDefault();
mIsHost = isHost();
}
@@ -215,18 +213,6 @@
mDestination.getPrefixLength() == 128);
}
- private boolean isDefault() {
- boolean val = false;
- if (mGateway != null) {
- if (mGateway instanceof Inet4Address) {
- val = (mDestination == null || mDestination.getPrefixLength() == 0);
- } else {
- val = (mDestination == null || mDestination.getPrefixLength() == 0);
- }
- }
- return val;
- }
-
/**
* Retrieves the destination address and prefix length in the form of an {@link IpPrefix}.
*
@@ -269,7 +255,23 @@
* @return {@code true} if the destination has a prefix length of 0.
*/
public boolean isDefaultRoute() {
- return mIsDefault;
+ return mDestination.getPrefixLength() == 0;
+ }
+
+ /**
+ * Indicates if this route is an IPv4 default route.
+ * @hide
+ */
+ public boolean isIPv4Default() {
+ return isDefaultRoute() && mDestination.getAddress() instanceof Inet4Address;
+ }
+
+ /**
+ * Indicates if this route is an IPv6 default route.
+ * @hide
+ */
+ public boolean isIPv6Default() {
+ return isDefaultRoute() && mDestination.getAddress() instanceof Inet6Address;
}
/**
@@ -370,10 +372,9 @@
* Returns a hashcode for this <code>RouteInfo</code> object.
*/
public int hashCode() {
- return (mDestination == null ? 0 : mDestination.hashCode() * 41)
+ return (mDestination.hashCode() * 41)
+ (mGateway == null ? 0 :mGateway.hashCode() * 47)
- + (mInterface == null ? 0 :mInterface.hashCode() * 67)
- + (mIsDefault ? 3 : 7);
+ + (mInterface == null ? 0 :mInterface.hashCode() * 67);
}
/**
@@ -387,13 +388,8 @@
* Implement the Parcelable interface
*/
public void writeToParcel(Parcel dest, int flags) {
- if (mDestination == null) {
- dest.writeByte((byte) 0);
- } else {
- dest.writeByte((byte) 1);
- dest.writeByteArray(mDestination.getAddress().getAddress());
- dest.writeInt(mDestination.getPrefixLength());
- }
+ dest.writeByteArray(mDestination.getAddress().getAddress());
+ dest.writeInt(mDestination.getPrefixLength());
if (mGateway == null) {
dest.writeByte((byte) 0);
@@ -415,17 +411,15 @@
int prefix = 0;
InetAddress gateway = null;
- if (in.readByte() == 1) {
- byte[] addr = in.createByteArray();
- prefix = in.readInt();
+ byte[] addr = in.createByteArray();
+ prefix = in.readInt();
- try {
- destAddr = InetAddress.getByAddress(addr);
- } catch (UnknownHostException e) {}
- }
+ try {
+ destAddr = InetAddress.getByAddress(addr);
+ } catch (UnknownHostException e) {}
if (in.readByte() == 1) {
- byte[] addr = in.createByteArray();
+ addr = in.createByteArray();
try {
gateway = InetAddress.getByAddress(addr);
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 0d69b3b..b839613 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -171,8 +171,6 @@
* A key to a boolean in the "extras" bundle of the cursor.
* The boolean indicates that the provider did not create a snippet and that the client asking
* for the snippet should do it (true means the snippeting was deferred to the client).
- *
- * @hide
*/
public static final String DEFERRED_SNIPPETING = "deferred_snippeting";
@@ -849,7 +847,6 @@
/**
* Reference to the row in the RawContacts table holding the contact name.
* <P>Type: INTEGER REFERENCES raw_contacts(_id)</P>
- * @hide
*/
public static final String NAME_RAW_CONTACT_ID = "name_raw_contact_id";
@@ -1609,8 +1606,7 @@
CONTENT_URI, "strequent");
/**
- * The content:// style URI for showing frequently contacted person listing.
- * @hide
+ * The content:// style URI for showing a list of frequently contacted people.
*/
public static final Uri CONTENT_FREQUENT_URI = Uri.withAppendedPath(
CONTENT_URI, "frequent");
@@ -2229,7 +2225,6 @@
* type. For applications that need to be aware of the data set, this can
* be used instead of account type to distinguish sets of data. This is
* never intended to be used for specifying accounts.
- * @hide
*/
public static final String ACCOUNT_TYPE_AND_DATA_SET = "account_type_and_data_set";
@@ -2273,8 +2268,6 @@
* The default value is "0"
* </p>
* <p>Type: INTEGER</p>
- *
- * @hide
*/
public static final String NAME_VERIFIED = "name_verified";
@@ -3793,8 +3786,6 @@
* The package name to use when creating {@link Resources} objects for
* this data row. This value is only designed for use when building user
* interfaces, and should not be used to infer the owner.
- *
- * @hide
*/
public static final String RES_PACKAGE = "res_package";
@@ -5096,12 +5087,10 @@
}
/**
- * Additional column returned by the {@link Contacts#CONTENT_FILTER_URI} providing the
- * explanation of why the filter matched the contact. Specifically, it contains the
- * data elements that matched the query. The overall number of words in the snippet
- * can be capped.
- *
- * @hide
+ * Additional column returned by
+ * {@link ContactsContract.Contacts#CONTENT_FILTER_URI Contacts.CONTENT_FILTER_URI} explaining
+ * why the filter matched the contact. Specifically, it contains the data elements that
+ * matched the query. The overall number of words in the snippet can be capped.
*/
public static class SearchSnippetColumns {
@@ -5111,8 +5100,6 @@
* <p>
* The snippet may contain (parts of) several data elements comprising
* the contact.
- *
- * @hide
*/
public static final String SNIPPET = "snippet";
@@ -5128,8 +5115,6 @@
* tokens can be returned in total. A negative number indicates how many
* tokens can be returned per occurrence of the search terms.</li>
* </ul>
- *
- * @hide
*/
public static final String SNIPPET_ARGS_PARAM_KEY = "snippet_args";
@@ -5140,8 +5125,6 @@
* {@link ContactsContract#DEFERRED_SNIPPETING} in the cursor. If it exists, the client
* should do its own snippeting. If it doesn't exist, the snippet column in the cursor
* should already contain a snippetized string.
- *
- * @hide
*/
public static final String DEFERRED_SNIPPETING_KEY = "deferred_snippeting";
}
@@ -7007,8 +6990,6 @@
* The package name to use when creating {@link Resources} objects for
* this group. This value is only designed for use when building user
* interfaces, and should not be used to infer the owner.
- *
- * @hide
*/
public static final String RES_PACKAGE = "res_package";
@@ -7016,8 +6997,6 @@
* The display title of this group to load as a resource from
* {@link #RES_PACKAGE}, which may be localized.
* <P>Type: TEXT</P>
- *
- * @hide
*/
public static final String TITLE_RES = "title_res";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e76d70f..01e1f8a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4582,12 +4582,6 @@
public static final String PAYMENT_SERVICE_SEARCH_URI = "payment_service_search_uri";
/**
- * If enabled, intercepted notifications will be displayed (not suppressed) in zen mode.
- * @hide
- */
- public static final String DISPLAY_INTERCEPTED_NOTIFICATIONS = "display_intercepted_notifications";
-
- /**
* If enabled, apps should try to skip any introductory hints on first launch. This might
* apply to users that are already familiar with the environment or temporary users.
* <p>
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index b02a79d..9da3a51 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -161,6 +161,7 @@
private boolean mFinished;
private boolean mCanDoze;
private boolean mDozing;
+ private boolean mWindowless;
private DozeHardware mDozeHardware;
private boolean mDebug = false;
@@ -520,6 +521,24 @@
}
/**
+ * Marks this dream as windowless. Only available to doze dreams.
+ *
+ * @hide
+ */
+ public void setWindowless(boolean windowless) {
+ mWindowless = windowless;
+ }
+
+ /**
+ * Returns whether or not this dream is windowless. Only available to doze dreams.
+ *
+ * @hide
+ */
+ public boolean isWindowless() {
+ return mWindowless;
+ }
+
+ /**
* Returns true if this dream is allowed to doze.
* <p>
* The value returned by this method is only meaningful when the dream has started.
@@ -715,6 +734,7 @@
WindowManagerGlobal.getInstance().closeAll(mWindowToken,
this.getClass().getName(), "Dream");
mWindowToken = null;
+ mCanDoze = false;
}
}
@@ -744,47 +764,50 @@
mWindowToken = windowToken;
mCanDoze = canDoze;
-
- mWindow = PolicyManager.makeNewWindow(this);
- mWindow.setCallback(this);
- mWindow.requestFeature(Window.FEATURE_NO_TITLE);
- mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));
- mWindow.setFormat(PixelFormat.OPAQUE);
-
- if (mDebug) Slog.v(TAG, String.format("Attaching window token: %s to window of type %s",
- windowToken, WindowManager.LayoutParams.TYPE_DREAM));
-
- WindowManager.LayoutParams lp = mWindow.getAttributes();
- lp.type = WindowManager.LayoutParams.TYPE_DREAM;
- lp.token = windowToken;
- lp.windowAnimations = com.android.internal.R.style.Animation_Dream;
- lp.flags |= ( WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
- | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
- | (mFullscreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0)
- | (mScreenBright ? WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON : 0)
- );
- mWindow.setAttributes(lp);
- mWindow.setWindowManager(null, windowToken, "dream", true);
-
- applySystemUiVisibilityFlags(
- (mLowProfile ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0),
- View.SYSTEM_UI_FLAG_LOW_PROFILE);
-
- try {
- getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes());
- } catch (WindowManager.BadTokenException ex) {
- // This can happen because the dream manager service will remove the token
- // immediately without necessarily waiting for the dream to start.
- // We should receive a finish message soon.
- Slog.i(TAG, "attach() called after window token already removed, dream will "
- + "finish soon");
- mWindow = null;
- return;
+ if (mWindowless && !mCanDoze) {
+ throw new IllegalStateException("Only doze dreams can be windowless");
}
+ if (!mWindowless) {
+ mWindow = PolicyManager.makeNewWindow(this);
+ mWindow.setCallback(this);
+ mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+ mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));
+ mWindow.setFormat(PixelFormat.OPAQUE);
+ if (mDebug) Slog.v(TAG, String.format("Attaching window token: %s to window of type %s",
+ windowToken, WindowManager.LayoutParams.TYPE_DREAM));
+
+ WindowManager.LayoutParams lp = mWindow.getAttributes();
+ lp.type = WindowManager.LayoutParams.TYPE_DREAM;
+ lp.token = windowToken;
+ lp.windowAnimations = com.android.internal.R.style.Animation_Dream;
+ lp.flags |= ( WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+ | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
+ | (mFullscreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0)
+ | (mScreenBright ? WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON : 0)
+ );
+ mWindow.setAttributes(lp);
+ mWindow.setWindowManager(null, windowToken, "dream", true);
+
+ applySystemUiVisibilityFlags(
+ (mLowProfile ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0),
+ View.SYSTEM_UI_FLAG_LOW_PROFILE);
+
+ try {
+ getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes());
+ } catch (WindowManager.BadTokenException ex) {
+ // This can happen because the dream manager service will remove the token
+ // immediately without necessarily waiting for the dream to start.
+ // We should receive a finish message soon.
+ Slog.i(TAG, "attach() called after window token already removed, dream will "
+ + "finish soon");
+ mWindow = null;
+ return;
+ }
+ }
// We need to defer calling onDreamingStarted until after onWindowAttached,
// which is posted to the handler by addView, so we post onDreamingStarted
// to the handler also. Need to watch out here in case detach occurs before
@@ -792,7 +815,7 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- if (mWindow != null) {
+ if (mWindow != null || mWindowless) {
if (mDebug) Slog.v(TAG, "Calling onDreamingStarted()");
mStarted = true;
onDreamingStarted();
@@ -878,6 +901,7 @@
if (isLowProfile()) pw.print(" lowprofile");
if (isFullscreen()) pw.print(" fullscreen");
if (isScreenBright()) pw.print(" bright");
+ if (isWindowless()) pw.print(" windowless");
if (isDozing()) pw.print(" dozing");
pw.println();
}
diff --git a/core/java/android/transition/Fade.java b/core/java/android/transition/Fade.java
index e70dc0c..71559da 100644
--- a/core/java/android/transition/Fade.java
+++ b/core/java/android/transition/Fade.java
@@ -63,23 +63,23 @@
/**
* Fading mode used in {@link #Fade(int)} to make the transition
* operate on targets that are appearing. Maybe be combined with
- * {@link #OUT} to fade both in and out.
+ * {@link #OUT} to fade both in and out. Equivalent to
+ * {@link Visibility#IN}.
*/
- public static final int IN = 0x1;
+ public static final int IN = Visibility.IN;
+
/**
* Fading mode used in {@link #Fade(int)} to make the transition
* operate on targets that are disappearing. Maybe be combined with
- * {@link #IN} to fade both in and out.
+ * {@link #IN} to fade both in and out. Equivalent to
+ * {@link Visibility#OUT}.
*/
- public static final int OUT = 0x2;
-
- private int mFadingMode;
+ public static final int OUT = Visibility.OUT;
/**
* Constructs a Fade transition that will fade targets in and out.
*/
public Fade() {
- this(IN | OUT);
}
/**
@@ -90,7 +90,7 @@
* {@link #IN} and {@link #OUT}.
*/
public Fade(int fadingMode) {
- mFadingMode = fadingMode;
+ setMode(fadingMode);
}
/**
@@ -115,9 +115,6 @@
public Animator onAppear(ViewGroup sceneRoot, View view,
TransitionValues startValues,
TransitionValues endValues) {
- if ((mFadingMode & IN) != IN || endValues == null) {
- return null;
- }
if (DBG) {
View startView = (startValues != null) ? startValues.view : null;
Log.d(LOG_TAG, "Fade.onAppear: startView, startVis, endView, endVis = " +
@@ -129,10 +126,6 @@
@Override
public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues,
TransitionValues endValues) {
- if ((mFadingMode & OUT) != OUT) {
- return null;
- }
-
return createAnimation(view, 1, 0);
}
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index 988d23d..52e6691 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -73,6 +73,11 @@
*
* {@sample development/samples/ApiDemos/res/transition/explode_move_together.xml MultipleTransform}
*
+ * <p>Custom transition classes may be instantiated with a <code>transition</code> tag:</p>
+ * <pre><transition class="my.app.transition.CustomTransition"/></pre>
+ * <p>Custom transition classes loaded from XML must have a public nullary (no argument)
+ * constructor.</p>
+ *
* <p>Note that attributes for the transition are not required, just as they are
* optional when declared in code; Transitions created from XML resources will use
* the same defaults as their code-created equivalents. Here is a slightly more
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index 1624188..e0c3cae 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -184,6 +184,8 @@
createTransitionFromXml(parser, attrs, ((TransitionSet) transition));
a.recycle();
newTransition = true;
+ } else if ("transition".equals(name)) {
+ transition = createCustomTransition(attrs);
} else if ("targets".equals(name)) {
if (parser.getDepth() - 1 > depth && transition != null) {
// We're inside the child tag - add targets to the child
@@ -208,6 +210,29 @@
return transition;
}
+ private Transition createCustomTransition(AttributeSet attrs) {
+ String className = attrs.getAttributeValue(null, "class");
+
+ if (className == null) {
+ throw new RuntimeException("transition tag must have a 'class' attribute");
+ }
+
+ try {
+ Class c = Class.forName(className);
+ if (!Transition.class.isAssignableFrom(c)) {
+ throw new RuntimeException("transition class must be a Transition: " + className);
+ }
+ return (Transition) c.newInstance();
+ } catch (InstantiationException e) {
+ throw new RuntimeException("Could not instantiate transition class", e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Could not access default constructor for transition class "
+ + className, e);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Could not find transition class " + className, e);
+ }
+ }
+
private Slide createSlideTransition(AttributeSet attrs) {
TypedArray a = mContext.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.Slide);
@@ -327,6 +352,16 @@
transition.setMatchOrder(parseMatchOrder(matchOrder));
}
a.recycle();
+ if (transition instanceof Visibility) {
+ a = mContext.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.VisibilityTransition);
+ int mode = a.getInt(
+ com.android.internal.R.styleable.VisibilityTransition_visibilityMode, 0);
+ a.recycle();
+ if (mode != 0) {
+ ((Visibility)transition).setMode(mode);
+ }
+ }
return transition;
}
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index 947e1a7..aa9f04e 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -43,6 +43,20 @@
private static final String PROPNAME_PARENT = "android:visibility:parent";
private static final String PROPNAME_SCREEN_LOCATION = "android:visibility:screenLocation";
+ /**
+ * Mode used in {@link #setMode(int)} to make the transition
+ * operate on targets that are appearing. Maybe be combined with
+ * {@link #OUT} to target Visibility changes both in and out.
+ */
+ public static final int IN = 0x1;
+
+ /**
+ * Mode used in {@link #setMode(int)} to make the transition
+ * operate on targets that are disappearing. Maybe be combined with
+ * {@link #IN} to target Visibility changes both in and out.
+ */
+ public static final int OUT = 0x2;
+
private static final String[] sTransitionProperties = {
PROPNAME_VISIBILITY,
PROPNAME_PARENT,
@@ -58,6 +72,22 @@
ViewGroup endParent;
}
+ private int mMode = IN | OUT;
+
+ /**
+ * Changes the transition to support appearing and/or disappearing Views, depending
+ * on <code>mode</code>.
+ *
+ * @param mode The behavior supported by this transition, a combination of
+ * {@link #IN} and {@link #OUT}.
+ */
+ public void setMode(int mode) {
+ if ((mode & ~(IN | OUT)) != 0) {
+ throw new IllegalArgumentException("Only IN and OUT flags are allowed");
+ }
+ mMode = mode;
+ }
+
@Override
public String[] getTransitionProperties() {
return sTransitionProperties;
@@ -200,6 +230,9 @@
public Animator onAppear(ViewGroup sceneRoot,
TransitionValues startValues, int startVisibility,
TransitionValues endValues, int endVisibility) {
+ if ((mMode & IN) != IN || endValues == null) {
+ return null;
+ }
return onAppear(sceneRoot, endValues.view, startValues, endValues);
}
@@ -260,6 +293,10 @@
public Animator onDisappear(ViewGroup sceneRoot,
TransitionValues startValues, int startVisibility,
TransitionValues endValues, int endVisibility) {
+ if ((mMode & OUT) != OUT) {
+ return null;
+ }
+
View startView = (startValues != null) ? startValues.view : null;
View endView = (endValues != null) ? endValues.view : null;
View overlayView = null;
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index b9ed801..577415e 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -311,7 +311,7 @@
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
- mFactory = new FactoryMerger(factory, factory, mFactory, mFactory2);
+ mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
@@ -319,7 +319,11 @@
* @hide for use by framework
*/
public void setPrivateFactory(Factory2 factory) {
- mPrivateFactory = factory;
+ if (mPrivateFactory == null) {
+ mPrivateFactory = factory;
+ } else {
+ mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
+ }
}
/**
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index d86b699..ddc185c 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -21,9 +21,6 @@
import android.graphics.Outline;
import android.graphics.Paint;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* <p>A display list records a series of graphics related operations and can replay
* them later. Display lists are usually built by recording operations on a
@@ -180,12 +177,6 @@
private boolean mValid;
private final long mNativeRenderNode;
- // We need to keep a strong reference to all running animators to ensure that
- // they can call removeAnimator when they have finished, as the native-side
- // object can only hold a WeakReference<> to avoid leaking memory due to
- // cyclic references.
- private List<RenderNodeAnimator> mActiveAnimators;
-
private RenderNode(String name) {
mNativeRenderNode = nCreate(name);
}
@@ -866,18 +857,9 @@
///////////////////////////////////////////////////////////////////////////
public void addAnimator(RenderNodeAnimator animator) {
- if (mActiveAnimators == null) {
- mActiveAnimators = new ArrayList<RenderNodeAnimator>();
- }
- mActiveAnimators.add(animator);
nAddAnimator(mNativeRenderNode, animator.getNativeAnimator());
}
- public void removeAnimator(RenderNodeAnimator animator) {
- nRemoveAnimator(mNativeRenderNode, animator.getNativeAnimator());
- mActiveAnimators.remove(animator);
- }
-
///////////////////////////////////////////////////////////////////////////
// Native methods
///////////////////////////////////////////////////////////////////////////
@@ -960,7 +942,6 @@
///////////////////////////////////////////////////////////////////////////
private static native void nAddAnimator(long renderNode, long animatorPtr);
- private static native void nRemoveAnimator(long renderNode, long animatorPtr);
///////////////////////////////////////////////////////////////////////////
// Finalization
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 4979059..1363a5c 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -172,12 +172,14 @@
@Override
public void cancel() {
- mTarget.removeAnimator(this);
+ if (!mFinished) {
+ nCancel(mNativePtr.get());
- final ArrayList<AnimatorListener> listeners = getListeners();
- final int numListeners = listeners == null ? 0 : listeners.size();
- for (int i = 0; i < numListeners; i++) {
- listeners.get(i).onAnimationCancel(this);
+ final ArrayList<AnimatorListener> listeners = getListeners();
+ final int numListeners = listeners == null ? 0 : listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ listeners.get(i).onAnimationCancel(this);
+ }
}
}
@@ -219,10 +221,6 @@
return mTarget;
}
- /**
- * WARNING: May only be called once!!!
- * TODO: Fix above -_-
- */
public void setStartValue(float startValue) {
checkMutable();
nSetStartValue(mNativePtr.get(), startValue);
@@ -231,6 +229,9 @@
@Override
public void setStartDelay(long startDelay) {
checkMutable();
+ if (startDelay < 0) {
+ throw new IllegalArgumentException("startDelay must be positive; " + startDelay);
+ }
nSetStartDelay(mNativePtr.get(), startDelay);
}
@@ -242,6 +243,9 @@
@Override
public RenderNodeAnimator setDuration(long duration) {
checkMutable();
+ if (duration < 0) {
+ throw new IllegalArgumentException("duration must be positive; " + duration);
+ }
nSetDuration(mNativePtr.get(), duration);
return this;
}
@@ -269,7 +273,6 @@
private void onFinished() {
mFinished = true;
- mTarget.removeAnimator(this);
final ArrayList<AnimatorListener> listeners = getListeners();
final int numListeners = listeners == null ? 0 : listeners.size();
@@ -296,10 +299,13 @@
long canvasProperty, float finalValue);
private static native long nCreateCanvasPropertyPaintAnimator(WeakReference<RenderNodeAnimator> weakThis,
long canvasProperty, int paintField, float finalValue);
+
private static native void nSetStartValue(long nativePtr, float startValue);
private static native void nSetDuration(long nativePtr, long duration);
private static native long nGetDuration(long nativePtr);
private static native void nSetStartDelay(long nativePtr, long startDelay);
private static native long nGetStartDelay(long nativePtr);
private static native void nSetInterpolator(long animPtr, long interpolatorPtr);
+
+ private static native void nCancel(long animPtr);
}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 5c67da9..bfab654 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -64,9 +64,9 @@
// Keep in sync with DrawFrameTask.h SYNC_* flags
// Nothing interesting to report
- private static final int SYNC_OK = 0x0;
+ private static final int SYNC_OK = 0;
// Needs a ViewRoot invalidate
- private static final int SYNC_INVALIDATE_REQUIRED = 0x1;
+ private static final int SYNC_INVALIDATE_REQUIRED = 1 << 0;
private static final String[] VISUALIZERS = {
PROFILE_PROPERTY_VISUALIZE_BARS,
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java
index 66f5f6c..d3d5fff 100644
--- a/core/java/android/view/inputmethod/CursorAnchorInfo.java
+++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java
@@ -21,6 +21,8 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Layout;
+import android.text.SpannedString;
+import android.text.TextUtils;
import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder;
import java.util.Objects;
@@ -40,7 +42,7 @@
/**
* The text, tracked as a composing region.
*/
- private final String mComposingText;
+ private final CharSequence mComposingText;
/**
* Horizontal position of the insertion marker, in the local coordinates that will be
@@ -88,7 +90,7 @@
mSelectionStart = source.readInt();
mSelectionEnd = source.readInt();
mComposingTextStart = source.readInt();
- mComposingText = source.readString();
+ mComposingText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
mInsertionMarkerHorizontal = source.readFloat();
mInsertionMarkerTop = source.readFloat();
mInsertionMarkerBaseline = source.readFloat();
@@ -109,7 +111,7 @@
dest.writeInt(mSelectionStart);
dest.writeInt(mSelectionEnd);
dest.writeInt(mComposingTextStart);
- dest.writeString(mComposingText);
+ TextUtils.writeToParcel(mComposingText, dest, flags);
dest.writeFloat(mInsertionMarkerHorizontal);
dest.writeFloat(mInsertionMarkerTop);
dest.writeFloat(mInsertionMarkerBaseline);
@@ -210,12 +212,13 @@
if (composingText == null) {
mComposingText = null;
} else {
- mComposingText = composingText.toString();
+ // Make a snapshot of the given char sequence.
+ mComposingText = new SpannedString(composingText);
}
return this;
}
private int mComposingTextStart = -1;
- private String mComposingText = null;
+ private CharSequence mComposingText = null;
/**
* Sets the location of the text insertion point (zero width cursor) as a rectangle in
@@ -362,7 +365,7 @@
* Returns the entire composing text.
* @return null if there is no composition.
*/
- public String getComposingText() {
+ public CharSequence getComposingText() {
return mComposingText;
}
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 321d9d3..2564ff0 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -68,7 +68,36 @@
throw new MustOverrideException();
}
- /**
+ /**
+ * Sets whether the {@link WebView} should allow third party cookies to be set.
+ * Allowing third party cookies is a per WebView policy and can be set
+ * differently on different WebView instances.
+ * <p>
+ * Apps that target {@link android.os.Build.VERSION_CODES#KITKAT} or below
+ * default to allowing third party cookies. Apps targeting
+ * {@link android.os.Build.VERSION_CODES#L} or later default to disallowing
+ * third party cookies.
+ *
+ * @param webview the {@link WebView} instance to set the cookie policy on
+ * @param accept whether the {@link WebView} instance should accept
+ * third party cookies
+ */
+ public synchronized void setAcceptThirdPartyCookies(WebView webview,
+ boolean accept) {
+ throw new MustOverrideException();
+ }
+
+ /**
+ * Gets whether the {@link WebView} should allow third party cookies to be set.
+ *
+ * @param webview the {@link WebView} instance to get the cookie policy for
+ * @return true if the {@link WebView} accepts third party cookies
+ */
+ public synchronized boolean acceptThirdPartyCookies(WebView webview) {
+ throw new MustOverrideException();
+ }
+
+ /**
* Sets a cookie for the given URL. Any existing cookie with the same host,
* path and name will be replaced with the new cookie. The cookie being set
* will be ignored if it is expired.
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index d14c19b..44301c3 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -500,6 +500,24 @@
}
/**
+ * Sets policy for third party cookies.
+ * Developers should access this via {@link CookieManager#setShouldAcceptThirdPartyCookies}.
+ * @hide Internal API.
+ */
+ public void setAcceptThirdPartyCookies(boolean accept) {
+ throw new MustOverrideException();
+ }
+
+ /**
+ * Gets policy for third party cookies.
+ * Developers should access this via {@link CookieManager#getShouldAcceptThirdPartyCookies}.
+ * @hide Internal API
+ */
+ public boolean getAcceptThirdPartyCookies() {
+ throw new MustOverrideException();
+ }
+
+ /**
* Sets the text size of the page. The default is {@link TextSize#NORMAL}.
*
* @param t the text size as a {@link TextSize} value
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 00e0d17..122df2c 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -635,6 +635,17 @@
}
/**
+ * Retrieve the currently configured content description for the navigation button view.
+ * This will be used to describe the navigation action to users through mechanisms such
+ * as screen readers or tooltips.
+ *
+ * @return The navigation button's content description
+ */
+ public CharSequence getNavigationContentDescription() {
+ return mNavButtonView != null ? mNavButtonView.getContentDescription() : null;
+ }
+
+ /**
* Set a content description for the navigation button if one is present. The content
* description will be read via screen readers or other accessibility systems to explain
* the action of the navigation button.
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index 58cd60d..e58d68f 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -442,8 +442,9 @@
final String[] procStatsString = mProcessFullStatsStringData;
final long[] procStats = mProcessFullStatsData;
st.base_uptime = SystemClock.uptimeMillis();
- if (Process.readProcFile(st.statFile.toString(),
- PROCESS_FULL_STATS_FORMAT, procStatsString,
+ String path = st.statFile.toString();
+ //Slog.d(TAG, "Reading proc file: " + path);
+ if (Process.readProcFile(path, PROCESS_FULL_STATS_FORMAT, procStatsString,
procStats, null)) {
// This is a possible way to filter out processes that
// are actually kernel threads... do we want to? Some
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index a01e9b7..84bd443 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -24,9 +24,6 @@
{
void setIcon(int index, in StatusBarIcon icon);
void removeIcon(int index);
- void addNotification(in StatusBarNotification notification);
- void updateNotification(in StatusBarNotification notification);
- void removeNotification(String key);
void disable(int state);
void animateExpandNotificationsPanel();
void animateExpandSettingsPanel();
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index a3b417f..f3430e7 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -39,8 +39,7 @@
// ---- Methods below are for use by the status bar policy services ----
// You need the STATUS_BAR_SERVICE permission
void registerStatusBar(IStatusBar callbacks, out StatusBarIconList iconList,
- out List<StatusBarNotification> notifications, out int[] switches,
- out List<IBinder> binders);
+ out int[] switches, out List<IBinder> binders);
void onPanelRevealed();
void onPanelHidden();
void onNotificationClick(String key);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index d31c5cc..5bd6f52 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1460,4 +1460,11 @@
}
return activeTrustAgents;
}
+
+ /**
+ * @see android.app.trust.TrustManager#reportRequireCredentialEntry(int)
+ */
+ public void requireCredentialEntry(int userId) {
+ getTrustManager().reportRequireCredentialEntry(userId);
+ }
}
diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
index 7fb2efd..30cd11b 100644
--- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
+++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
@@ -152,6 +152,11 @@
a.recycle();
+ if (TextUtils.isEmpty(mToolbar.getNavigationContentDescription())) {
+ mToolbar.setNavigationContentDescription(
+ getContext().getResources().getText(R.string.action_bar_up_description));
+ }
+
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(),
0, android.R.id.home, 0, 0, mTitle);
diff --git a/core/java/com/android/server/net/NetlinkTracker.java b/core/java/com/android/server/net/NetlinkTracker.java
index 7dd8dd8..ff905bb 100644
--- a/core/java/com/android/server/net/NetlinkTracker.java
+++ b/core/java/com/android/server/net/NetlinkTracker.java
@@ -21,6 +21,16 @@
import android.net.RouteInfo;
import android.util.Log;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
/**
* Keeps track of link configuration received from Netlink.
*
@@ -67,6 +77,7 @@
private final String mInterfaceName;
private final Callback mCallback;
private final LinkProperties mLinkProperties;
+ private DnsServerRepository mDnsServerRepository;
private static final boolean DBG = true;
@@ -76,6 +87,7 @@
mCallback = callback;
mLinkProperties = new LinkProperties();
mLinkProperties.setInterfaceName(mInterfaceName);
+ mDnsServerRepository = new DnsServerRepository();
}
private void maybeLog(String operation, String iface, LinkAddress address) {
@@ -147,6 +159,20 @@
}
}
+ @Override
+ public void interfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
+ if (mInterfaceName.equals(iface)) {
+ maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses));
+ boolean changed = mDnsServerRepository.addServers(lifetime, addresses);
+ if (changed) {
+ synchronized (this) {
+ mDnsServerRepository.setDnsServersOn(mLinkProperties);
+ }
+ mCallback.update();
+ }
+ }
+ }
+
/**
* Returns a copy of this object's LinkProperties.
*/
@@ -155,7 +181,185 @@
}
public synchronized void clearLinkProperties() {
+ // Clear the repository before clearing mLinkProperties. That way, if a clear() happens
+ // while interfaceDnsServerInfo() is being called, we'll end up with no DNS servers in
+ // mLinkProperties, as desired.
+ mDnsServerRepository = new DnsServerRepository();
mLinkProperties.clear();
mLinkProperties.setInterfaceName(mInterfaceName);
}
}
+
+/**
+ * Represents a DNS server entry with an expiry time.
+ *
+ * Implements Comparable so DNS server entries can be sorted by lifetime, longest-lived first.
+ * The ordering of entries with the same lifetime is unspecified, because given two servers with
+ * identical lifetimes, we don't care which one we use, and only comparing the lifetime is much
+ * faster than comparing the IP address as well.
+ *
+ * Note: this class has a natural ordering that is inconsistent with equals.
+ */
+class DnsServerEntry implements Comparable<DnsServerEntry> {
+ /** The IP address of the DNS server. */
+ public final InetAddress address;
+ /** The time until which the DNS server may be used. A Java millisecond time as might be
+ * returned by currentTimeMillis(). */
+ public long expiry;
+
+ public DnsServerEntry(InetAddress address, long expiry) throws IllegalArgumentException {
+ this.address = address;
+ this.expiry = expiry;
+ }
+
+ public int compareTo(DnsServerEntry other) {
+ return Long.compare(other.expiry, this.expiry);
+ }
+}
+
+/**
+ * Tracks DNS server updates received from Netlink.
+ *
+ * The network may announce an arbitrary number of DNS servers in Router Advertisements at any
+ * time. Each announcement has a lifetime; when the lifetime expires, the servers should not be used
+ * any more. In this way, the network can gracefully migrate clients from one set of DNS servers to
+ * another. Announcements can both raise and lower the lifetime, and an announcement can expire
+ * servers by announcing them with a lifetime of zero.
+ *
+ * Typically the system will only use a small number (2 or 3; {@code NUM_CURRENT_SERVERS}) of DNS
+ * servers at any given time. These are referred to as the current servers. In case all the
+ * current servers expire, the class also keeps track of a larger (but limited) number of servers
+ * that are promoted to current servers when the current ones expire. In order to minimize updates
+ * to the rest of the system (and potentially expensive cache flushes) this class attempts to keep
+ * the list of current servers constant where possible. More specifically, the list of current
+ * servers is only updated if a new server is learned and there are not yet {@code
+ * NUM_CURRENT_SERVERS} current servers, or if one or more of the current servers expires or is
+ * pushed out of the set. Therefore, the current servers will not necessarily be the ones with the
+ * highest lifetime, but the ones learned first.
+ *
+ * This is by design: if instead the class always preferred the servers with the highest lifetime, a
+ * (misconfigured?) network where two or more routers announce more than {@code NUM_CURRENT_SERVERS}
+ * unique servers would cause persistent oscillations.
+ *
+ * TODO: Currently servers are only expired when a new DNS update is received.
+ * Update them using timers, or possibly on every notification received by NetlinkTracker.
+ *
+ * Threading model: run by NetlinkTracker. Methods are synchronized(this) just in case netlink
+ * notifications are sent by multiple threads. If future threads use alarms to expire, those
+ * alarms must also be synchronized(this).
+ *
+ */
+class DnsServerRepository {
+
+ /** How many DNS servers we will use. 3 is suggested by RFC 6106. */
+ public static final int NUM_CURRENT_SERVERS = 3;
+
+ /** How many DNS servers we'll keep track of, in total. */
+ public static final int NUM_SERVERS = 12;
+
+ /** Stores up to {@code NUM_CURRENT_SERVERS} DNS servers we're currently using. */
+ private Set<InetAddress> mCurrentServers;
+
+ public static final String TAG = "DnsServerRepository";
+
+ /**
+ * Stores all the DNS servers we know about, for use when the current servers expire.
+ * Always sorted in order of decreasing expiry. The elements in this list are also the values
+ * of mIndex, and may be elements in mCurrentServers.
+ */
+ private ArrayList<DnsServerEntry> mAllServers;
+
+ /**
+ * Indexes the servers so we can update their lifetimes more quickly in the common case where
+ * servers are not being added, but only being refreshed.
+ */
+ private HashMap<InetAddress, DnsServerEntry> mIndex;
+
+ public DnsServerRepository() {
+ mCurrentServers = new HashSet();
+ mAllServers = new ArrayList<DnsServerEntry>(NUM_SERVERS);
+ mIndex = new HashMap<InetAddress, DnsServerEntry>(NUM_SERVERS);
+ }
+
+ /** Sets the DNS servers of the provided LinkProperties object to the current servers. */
+ public synchronized void setDnsServersOn(LinkProperties lp) {
+ lp.setDnsServers(mCurrentServers);
+ }
+
+ /**
+ * Notifies the class of new DNS server information.
+ * @param lifetime the time in seconds that the DNS servers are valid.
+ * @param addresses the string representations of the IP addresses of the DNS servers to use.
+ */
+ public synchronized boolean addServers(long lifetime, String[] addresses) {
+ // The lifetime is actually an unsigned 32-bit number, but Java doesn't have unsigned.
+ // Technically 0xffffffff (the maximum) is special and means "forever", but 2^32 seconds
+ // (136 years) is close enough.
+ long now = System.currentTimeMillis();
+ long expiry = now + 1000 * lifetime;
+
+ // Go through the list of servers. For each one, update the entry if one exists, and
+ // create one if it doesn't.
+ for (String addressString : addresses) {
+ InetAddress address;
+ try {
+ address = InetAddress.parseNumericAddress(addressString);
+ } catch (IllegalArgumentException ex) {
+ continue;
+ }
+
+ if (!updateExistingEntry(address, expiry)) {
+ // There was no entry for this server. Create one, unless it's already expired
+ // (i.e., if the lifetime is zero; it cannot be < 0 because it's unsigned).
+ if (expiry > now) {
+ DnsServerEntry entry = new DnsServerEntry(address, expiry);
+ mAllServers.add(entry);
+ mIndex.put(address, entry);
+ }
+ }
+ }
+
+ // Sort the servers by expiry.
+ Collections.sort(mAllServers);
+
+ // Prune excess entries and update the current server list.
+ return updateCurrentServers();
+ }
+
+ private synchronized boolean updateExistingEntry(InetAddress address, long expiry) {
+ DnsServerEntry existing = mIndex.get(address);
+ if (existing != null) {
+ existing.expiry = expiry;
+ return true;
+ }
+ return false;
+ }
+
+ private synchronized boolean updateCurrentServers() {
+ long now = System.currentTimeMillis();
+ boolean changed = false;
+
+ // Prune excess or expired entries.
+ for (int i = mAllServers.size() - 1; i >= 0; i--) {
+ if (i >= NUM_SERVERS || mAllServers.get(i).expiry < now) {
+ DnsServerEntry removed = mAllServers.remove(i);
+ mIndex.remove(removed.address);
+ changed |= mCurrentServers.remove(removed.address);
+ } else {
+ break;
+ }
+ }
+
+ // Add servers to the current set, in order of decreasing lifetime, until it has enough.
+ // Prefer existing servers over new servers in order to minimize updates to the rest of the
+ // system and avoid persistent oscillations.
+ for (DnsServerEntry entry : mAllServers) {
+ if (mCurrentServers.size() < NUM_CURRENT_SERVERS) {
+ changed |= mCurrentServers.add(entry.address);
+ } else {
+ break;
+ }
+ }
+ return changed;
+ }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index cb00062..f287fca 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -105,6 +105,8 @@
android/graphics/Interpolator.cpp \
android/graphics/MaskFilter.cpp \
android/graphics/Matrix.cpp \
+ android/graphics/MinikinSkia.cpp \
+ android/graphics/MinikinUtils.cpp \
android/graphics/Movie.cpp \
android/graphics/NinePatch.cpp \
android/graphics/NinePatchImpl.cpp \
@@ -120,8 +122,6 @@
android/graphics/Region.cpp \
android/graphics/Shader.cpp \
android/graphics/SurfaceTexture.cpp \
- android/graphics/TextLayout.cpp \
- android/graphics/TextLayoutCache.cpp \
android/graphics/Typeface.cpp \
android/graphics/TypefaceImpl.cpp \
android/graphics/Utils.cpp \
@@ -197,6 +197,9 @@
frameworks/opt/emoji \
libcore/include \
$(call include-path-for, audio-utils) \
+ frameworks/minikin/include \
+ external/freetype/include
+# TODO: clean up Minikin so it doesn't need the freetype include
LOCAL_SHARED_LIBRARIES := \
libmemtrack \
@@ -237,23 +240,14 @@
libpdfium \
libimg_utils \
libnetd_client \
- libsoundtrigger
+ libsoundtrigger \
+ libminikin \
+ libstlport
ifeq ($(USE_OPENGL_RENDERER),true)
LOCAL_SHARED_LIBRARIES += libhwui
endif
-ifeq ($(USE_MINIKIN), true)
- LOCAL_CFLAGS += -DUSE_MINIKIN
- LOCAL_C_INCLUDES += frameworks/minikin/include \
- external/freetype/include
- LOCAL_SRC_FILES += android/graphics/MinikinSkia.cpp \
- android/graphics/MinikinUtils.cpp
-# note: the freetype include is spurious; minikin itself probably
-# shouldn't depend on it
- LOCAL_SHARED_LIBRARIES += libminikin libstlport
-endif
-
LOCAL_SHARED_LIBRARIES += \
libdl
# we need to access the private Bionic header
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 9e09280..3f323ab 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -29,14 +29,10 @@
#include "SkTArray.h"
#include "SkTemplates.h"
-#ifdef USE_MINIKIN
#include <minikin/Layout.h>
#include "MinikinSkia.h"
#include "MinikinUtils.h"
-#endif
-#include "TextLayout.h"
-#include "TextLayoutCache.h"
#include "TypefaceImpl.h"
#include "unicode/ubidi.h"
@@ -301,7 +297,7 @@
}
static void freeTextLayoutCaches(JNIEnv* env, jobject) {
- TextLayoutEngine::getInstance().purgeCaches();
+ Layout::purgeCaches();
}
static jboolean isOpaque(JNIEnv*, jobject, jlong canvasHandle) {
@@ -904,7 +900,6 @@
env->ReleaseStringChars(text, textArray);
}
-#ifdef USE_MINIKIN
class DrawTextFunctor {
public:
DrawTextFunctor(const Layout& layout, SkCanvas* canvas, jfloat x, jfloat y, SkPaint* paint,
@@ -946,7 +941,6 @@
delete[] glyphs;
delete[] pos;
}
-#endif
static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray,
int start, int end,
@@ -961,29 +955,10 @@
int start, int count, int contextCount,
jfloat x, jfloat y, int bidiFlags, SkPaint* paint, TypefaceImpl* typeface) {
-#ifdef USE_MINIKIN
Layout layout;
std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
layout.doLayout(textArray, start, count, contextCount, css);
drawGlyphsToSkia(canvas, paint, layout, x, y);
-#else
- sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
- textArray, start, count, contextCount, bidiFlags);
- if (value == NULL) {
- return;
- }
- SkPaint::Align align = paint->getTextAlign();
- if (align == SkPaint::kCenter_Align) {
- x -= 0.5 * value->getTotalAdvance();
- } else if (align == SkPaint::kRight_Align) {
- x -= value->getTotalAdvance();
- }
- paint->setTextAlign(SkPaint::kLeft_Align);
- doDrawGlyphsPos(canvas, value->getGlyphs(), value->getPos(), 0, value->getGlyphsCount(),
- x, y, paint);
- doDrawTextDecorations(canvas, x, y, value->getTotalAdvance(), paint);
- paint->setTextAlign(align);
-#endif
}
// Same values used by Skia
@@ -1124,7 +1099,6 @@
delete[] posPtr;
}
-#ifdef USE_MINIKIN
class DrawTextOnPathFunctor {
public:
DrawTextOnPathFunctor(const Layout& layout, SkCanvas* canvas, float hOffset,
@@ -1149,11 +1123,9 @@
SkPaint* paint;
SkPath* path;
};
-#endif
static void doDrawTextOnPath(SkPaint* paint, const jchar* text, int count, int bidiFlags,
float hOffset, float vOffset, SkPath* path, SkCanvas* canvas, TypefaceImpl* typeface) {
-#ifdef USE_MINIKIN
Layout layout;
std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
layout.doLayout(text, 0, count, count, css);
@@ -1167,9 +1139,6 @@
DrawTextOnPathFunctor f(layout, canvas, hOffset, vOffset, paint, path);
MinikinUtils::forFontRun(layout, paint, f);
paint->setTextAlign(align);
-#else
- TextLayout::drawTextOnPath(paint, text, count, bidiFlags, hOffset, vOffset, path, canvas);
-#endif
}
static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject,
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index dd6b36f..1d465b3 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -27,44 +27,34 @@
#include <androidfw/AssetManager.h>
#include "Utils.h"
-#ifdef USE_MINIKIN
+#include "TypefaceImpl.h"
#include <minikin/FontFamily.h>
#include "MinikinSkia.h"
-#endif
namespace android {
static jlong FontFamily_create(JNIEnv* env, jobject clazz, jstring lang, jint variant) {
-#ifdef USE_MINIKIN
FontLanguage fontLanguage;
if (lang != NULL) {
ScopedUtfChars str(env, lang);
fontLanguage = FontLanguage(str.c_str(), str.size());
}
return (jlong)new FontFamily(fontLanguage, variant);
-#else
- return 0;
-#endif
}
static void FontFamily_unref(JNIEnv* env, jobject clazz, jlong familyPtr) {
-#ifdef USE_MINIKIN
FontFamily* fontFamily = reinterpret_cast<FontFamily*>(familyPtr);
fontFamily->Unref();
-#endif
}
-#ifdef USE_MINIKIN
static jboolean addSkTypeface(FontFamily* family, SkTypeface* face) {
MinikinFont* minikinFont = new MinikinFontSkia(face);
bool result = family->addFont(minikinFont);
minikinFont->Unref();
return result;
}
-#endif
static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong familyPtr, jstring path) {
-#ifdef USE_MINIKIN
NPE_CHECK_RETURN_ZERO(env, path);
ScopedUtfChars str(env, path);
SkTypeface* face = SkTypeface::CreateFromFile(str.c_str());
@@ -74,14 +64,10 @@
}
FontFamily* fontFamily = (FontFamily*)familyPtr;
return addSkTypeface(fontFamily, face);
-#else
- return false;
-#endif
}
static jboolean FontFamily_addFontFromAsset(JNIEnv* env, jobject, jlong familyPtr,
jobject jassetMgr, jstring jpath) {
-#ifdef USE_MINIKIN
NPE_CHECK_RETURN_ZERO(env, jassetMgr);
NPE_CHECK_RETURN_ZERO(env, jpath);
@@ -108,9 +94,6 @@
}
FontFamily* fontFamily = (FontFamily*)familyPtr;
return addSkTypeface(fontFamily, face);
-#else
- return false;
-#endif
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index ad174f7..7fda3d9 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -8,13 +8,16 @@
#include "SkPoint.h"
#include "SkRect.h"
#include "SkImageDecoder.h"
-#include "TypefaceImpl.h"
#include <jni.h>
class SkBitmapRegionDecoder;
class SkCanvas;
class SkPaint;
+namespace android {
+class TypefaceImpl;
+}
+
class GraphicsJNI {
public:
enum BitmapCreateFlags {
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index fc92d53..f02f118 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -59,7 +59,6 @@
SkPaintOptionsAndroid::FontVariant var = paint->getPaintOptionsAndroid().getFontVariant();
const char* varstr = var == SkPaintOptionsAndroid::kElegant_Variant ? "elegant" : "compact";
off = snprintfcat(css, off, sizeof(css), " -minikin-variant: %s;", varstr);
- layout->setProperties(css);
return std::string(css);
}
diff --git a/core/jni/android/graphics/MinikinUtils.h b/core/jni/android/graphics/MinikinUtils.h
index b2662a1..1663860 100644
--- a/core/jni/android/graphics/MinikinUtils.h
+++ b/core/jni/android/graphics/MinikinUtils.h
@@ -26,6 +26,18 @@
namespace android {
+// TODO: these should be defined in Minikin's Layout.h
+enum {
+ kBidi_LTR = 0,
+ kBidi_RTL = 1,
+ kBidi_Default_LTR = 2,
+ kBidi_Default_RTL = 3,
+ kBidi_Force_LTR = 4,
+ kBidi_Force_RTL = 5,
+
+ kBidi_Mask = 0x7
+};
+
class Layout;
class TypefaceImpl;
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index dc30814..0ad390a 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -34,14 +34,12 @@
#include "unicode/uloc.h"
#include "unicode/ushape.h"
#include "utils/Blur.h"
-#include "TextLayout.h"
-#ifdef USE_MINIKIN
#include <minikin/GraphemeBreak.h>
#include <minikin/Layout.h>
#include "MinikinSkia.h"
#include "MinikinUtils.h"
-#endif
+#include "TypefaceImpl.h"
// temporary for debugging
#include <Caches.h>
@@ -304,14 +302,8 @@
}
static jlong setTypeface(JNIEnv* env, jobject clazz, jlong objHandle, jlong typefaceHandle) {
-#ifndef USE_MINIKIN
- SkPaint* obj = reinterpret_cast<SkPaint*>(objHandle);
- SkTypeface* typeface = reinterpret_cast<SkTypeface*>(typefaceHandle);
- return reinterpret_cast<jlong>(obj->setTypeface(typeface));
-#else
- // TODO(raph): not yet implemented
+ // TODO: in Paint refactoring, set typeface on android Paint, not SkPaint
return NULL;
-#endif
}
static jlong setRasterizer(JNIEnv* env, jobject clazz, jlong objHandle, jlong rasterizerHandle) {
@@ -437,22 +429,18 @@
const int kElegantDescent = -500;
const int kElegantLeading = 0;
SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
-#ifdef USE_MINIKIN
TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint);
typeface = TypefaceImpl_resolveDefault(typeface);
FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
float saveSkewX = paint->getTextSkewX();
bool savefakeBold = paint->isFakeBoldText();
MinikinFontSkia::populateSkPaint(paint, baseFont.font, baseFont.fakery);
-#endif
SkScalar spacing = paint->getFontMetrics(metrics);
-#ifdef USE_MINIKIN
// The populateSkPaint call may have changed fake bold / text skew
// because we want to measure with those effects applied, so now
// restore the original settings.
paint->setTextSkewX(saveSkewX);
paint->setFakeBoldText(savefakeBold);
-#endif
SkPaintOptionsAndroid paintOpts = paint->getPaintOptionsAndroid();
if (paintOpts.getFontVariant() == SkPaintOptionsAndroid::kElegant_Variant) {
SkScalar size = paint->getTextSize();
@@ -534,17 +522,11 @@
const jchar* textArray = env->GetCharArrayElements(text, NULL);
jfloat result = 0;
-#ifdef USE_MINIKIN
Layout layout;
TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint);
std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
layout.doLayout(textArray, index, count, textLength, css);
result = layout.getAdvance();
-#else
- TextLayout::getTextRunAdvances(paint, textArray, index, count, textLength,
- bidiFlags, NULL /* dont need all advances */, &result);
-#endif
-
env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
return result;
}
@@ -568,16 +550,11 @@
SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
jfloat width = 0;
-#ifdef USE_MINIKIN
Layout layout;
TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint);
std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
layout.doLayout(textArray, start, count, textLength, css);
width = layout.getAdvance();
-#else
- TextLayout::getTextRunAdvances(paint, textArray, start, count, textLength,
- bidiFlags, NULL /* dont need all advances */, &width);
-#endif
env->ReleaseStringChars(text, textArray);
return width;
@@ -596,16 +573,11 @@
SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
jfloat width = 0;
-#ifdef USE_MINIKIN
Layout layout;
TypefaceImpl* typeface = GraphicsJNI::getNativeTypeface(env, jpaint);
std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
layout.doLayout(textArray, 0, textLength, textLength, css);
width = layout.getAdvance();
-#else
- TextLayout::getTextRunAdvances(paint, textArray, 0, textLength, textLength,
- bidiFlags, NULL /* dont need all advances */, &width);
-#endif
env->ReleaseStringChars(text, textArray);
return width;
@@ -632,15 +604,10 @@
AutoJavaFloatArray autoWidths(env, widths, count);
jfloat* widthsArray = autoWidths.ptr();
-#ifdef USE_MINIKIN
Layout layout;
std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
layout.doLayout(text, 0, count, count, css);
layout.getAdvances(widthsArray);
-#else
- TextLayout::getTextRunAdvances(paint, text, 0, count, count,
- bidiFlags, widthsArray, NULL /* dont need totalAdvance */);
-#endif
return count;
}
@@ -666,47 +633,6 @@
return count;
}
- static int doTextGlyphs(JNIEnv* env, SkPaint* paint, const jchar* text, jint start, jint count,
- jint contextCount, jint flags, jcharArray glyphs) {
- NPE_CHECK_RETURN_ZERO(env, paint);
- NPE_CHECK_RETURN_ZERO(env, text);
-
- if ((start | count | contextCount) < 0 || contextCount < count || !glyphs) {
- doThrowAIOOBE(env);
- return 0;
- }
- if (count == 0) {
- return 0;
- }
- size_t glypthsLength = env->GetArrayLength(glyphs);
- if ((size_t)count > glypthsLength) {
- doThrowAIOOBE(env);
- return 0;
- }
-
- jchar* glyphsArray = env->GetCharArrayElements(glyphs, NULL);
-
- sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
- text, start, count, contextCount, flags);
- const jchar* shapedGlyphs = value->getGlyphs();
- size_t glyphsCount = value->getGlyphsCount();
- memcpy(glyphsArray, shapedGlyphs, sizeof(jchar) * glyphsCount);
-
- env->ReleaseCharArrayElements(glyphs, glyphsArray, JNI_ABORT);
- return glyphsCount;
- }
-
- static jint getTextGlyphs__StringIIIII_C(JNIEnv* env, jobject clazz, jlong paintHandle,
- jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags,
- jcharArray glyphs) {
- SkPaint* paint = reinterpret_cast<SkPaint*>(paintHandle);
- const jchar* textArray = env->GetStringChars(text, NULL);
- int count = doTextGlyphs(env, paint, textArray + contextStart, start - contextStart,
- end - start, contextEnd - contextStart, flags, glyphs);
- env->ReleaseStringChars(text, textArray);
- return count;
- }
-
static jfloat doTextRunAdvances(JNIEnv *env, SkPaint *paint, TypefaceImpl* typeface, const jchar *text,
jint start, jint count, jint contextCount, jboolean isRtl,
jfloatArray advances, jint advancesIndex) {
@@ -732,16 +658,11 @@
int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
-#ifdef USE_MINIKIN
Layout layout;
std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
layout.doLayout(text, start, count, contextCount, css);
layout.getAdvances(advancesArray);
totalAdvance = layout.getAdvance();
-#else
- TextLayout::getTextRunAdvances(paint, text, start, count, contextCount, bidiFlags,
- advancesArray, &totalAdvance);
-#endif
if (advances != NULL) {
env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray);
@@ -779,52 +700,9 @@
static jint doTextRunCursor(JNIEnv *env, SkPaint* paint, const jchar *text, jint start,
jint count, jint flags, jint offset, jint opt) {
-#ifdef USE_MINIKIN
GraphemeBreak::MoveOpt moveOpt = GraphemeBreak::MoveOpt(opt);
size_t result = GraphemeBreak::getTextRunCursor(text, start, count, offset, moveOpt);
return static_cast<jint>(result);
-#else
- jfloat scalarArray[count];
-
- TextLayout::getTextRunAdvances(paint, text, start, count, start + count, flags,
- scalarArray, NULL /* dont need totalAdvance */);
-
- jint pos = offset - start;
- switch (opt) {
- case AFTER:
- if (pos < count) {
- pos += 1;
- }
- // fall through
- case AT_OR_AFTER:
- while (pos < count && scalarArray[pos] == 0) {
- ++pos;
- }
- break;
- case BEFORE:
- if (pos > 0) {
- --pos;
- }
- // fall through
- case AT_OR_BEFORE:
- while (pos > 0 && scalarArray[pos] == 0) {
- --pos;
- }
- break;
- case AT:
- default:
- if (scalarArray[pos] == 0) {
- pos = -1;
- }
- break;
- }
-
- if (pos != -1) {
- pos += start;
- }
-
- return pos;
-#endif
}
static jint getTextRunCursor___C(JNIEnv* env, jobject clazz, jlong paintHandle, jcharArray text,
@@ -847,7 +725,6 @@
return result;
}
-#ifdef USE_MINIKIN
class GetTextFunctor {
public:
GetTextFunctor(const Layout& layout, SkPath* path, jfloat x, jfloat y, SkPaint* paint,
@@ -878,11 +755,9 @@
SkPoint* pos;
SkPath tmpPath;
};
-#endif
static void getTextPath(JNIEnv* env, SkPaint* paint, TypefaceImpl* typeface, const jchar* text,
jint count, jint bidiFlags, jfloat x, jfloat y, SkPath* path) {
-#ifdef USE_MINIKIN
Layout layout;
std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
layout.doLayout(text, 0, count, count, css);
@@ -899,9 +774,6 @@
paint->setTextAlign(align);
delete[] glyphs;
delete[] pos;
-#else
- TextLayout::getTextPath(paint, text, count, bidiFlags, x, y, path);
-#endif
}
static void getTextPath___C(JNIEnv* env, jobject clazz, jlong paintHandle,
@@ -949,7 +821,6 @@
size_t measuredCount = 0;
float measured = 0;
-#ifdef USE_MINIKIN
Layout layout;
std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface);
layout.doLayout(text, 0, count, count, css);
@@ -970,19 +841,6 @@
measured += width;
}
delete[] advances;
-#else
- sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(&paint,
- text, 0, count, count, bidiFlags);
- if (value == NULL) {
- return 0;
- }
- SkScalar m;
- size_t bytes = paint.breakText(value->getGlyphs(), value->getGlyphsCount() << 1,
- maxWidth, &m, textBufferDirection);
- SkASSERT((bytes & 1) == 0);
- measuredCount = bytes >> 1;
- measured = SkScalarToFloat(m);
-#endif
if (jmeasured && env->GetArrayLength(jmeasured) > 0) {
AutoJavaFloatArray autoMeasured(env, jmeasured, 1);
@@ -1044,7 +902,6 @@
SkRect r;
SkIRect ir;
-#ifdef USE_MINIKIN
Layout layout;
std::string css = MinikinUtils::setLayoutProperties(&layout, &paint, bidiFlags, typeface);
layout.doLayout(text, 0, count, count, css);
@@ -1054,14 +911,6 @@
r.fTop = rect.mTop;
r.fRight = rect.mRight;
r.fBottom = rect.mBottom;
-#else
- sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(&paint,
- text, 0, count, count, bidiFlags);
- if (value == NULL) {
- return;
- }
- paint.measureText(value->getGlyphs(), value->getGlyphsCount() << 1, &r);
-#endif
r.roundOut(&ir);
GraphicsJNI::irect_to_jrect(ir, env, bounds);
}
@@ -1154,9 +1003,6 @@
{"native_getTextRunAdvances","(JJLjava/lang/String;IIIIZ[FI)F",
(void*) SkPaintGlue::getTextRunAdvances__StringIIIIZ_FI},
-
- {"native_getTextGlyphs","(JLjava/lang/String;IIIII[C)I",
- (void*) SkPaintGlue::getTextGlyphs__StringIIIII_C},
{"native_getTextRunCursor", "(J[CIIIII)I", (void*) SkPaintGlue::getTextRunCursor___C},
{"native_getTextRunCursor", "(JLjava/lang/String;IIIII)I",
(void*) SkPaintGlue::getTextRunCursor__String},
diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp
index 621534e..eea16f1 100644
--- a/core/jni/android/graphics/SurfaceTexture.cpp
+++ b/core/jni/android/graphics/SurfaceTexture.cpp
@@ -227,7 +227,7 @@
}
}
-static void SurfaceTexture_init(JNIEnv* env, jobject thiz,
+static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jboolean isDetached,
jint texName, jboolean singleBufferMode, jobject weakThiz)
{
sp<IGraphicBufferProducer> producer;
@@ -239,8 +239,15 @@
consumer->setDefaultMaxBufferCount(1);
}
- sp<GLConsumer> surfaceTexture(new GLConsumer(consumer, texName,
- GL_TEXTURE_EXTERNAL_OES, true, true));
+ sp<GLConsumer> surfaceTexture;
+ if (isDetached) {
+ surfaceTexture = new GLConsumer(consumer, GL_TEXTURE_EXTERNAL_OES,
+ true, true);
+ } else {
+ surfaceTexture = new GLConsumer(consumer, texName,
+ GL_TEXTURE_EXTERNAL_OES, true, true);
+ }
+
if (surfaceTexture == 0) {
jniThrowException(env, OutOfResourcesException,
"Unable to create native SurfaceTexture");
@@ -338,7 +345,7 @@
static JNINativeMethod gSurfaceTextureMethods[] = {
{"nativeClassInit", "()V", (void*)SurfaceTexture_classInit },
- {"nativeInit", "(IZLjava/lang/ref/WeakReference;)V", (void*)SurfaceTexture_init },
+ {"nativeInit", "(ZIZLjava/lang/ref/WeakReference;)V", (void*)SurfaceTexture_init },
{"nativeFinalize", "()V", (void*)SurfaceTexture_finalize },
{"nativeSetDefaultBufferSize", "(II)V", (void*)SurfaceTexture_setDefaultBufferSize },
{"nativeUpdateTexImage", "()V", (void*)SurfaceTexture_updateTexImage },
diff --git a/core/jni/android/graphics/TextLayout.cpp b/core/jni/android/graphics/TextLayout.cpp
deleted file mode 100644
index d0b6f7c..0000000
--- a/core/jni/android/graphics/TextLayout.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "TextLayout"
-
-#include "TextLayout.h"
-#include "TextLayoutCache.h"
-
-#include <android_runtime/AndroidRuntime.h>
-
-#include "SkTemplates.h"
-#include "unicode/ubidi.h"
-#include "unicode/ushape.h"
-#include <utils/Log.h>
-
-namespace android {
-
-// Returns true if we might need layout. If bidiFlags force LTR, assume no layout, if
-// bidiFlags indicate there probably is RTL, assume we do, otherwise scan the text
-// looking for a character >= the first RTL character in unicode and assume we do if
-// we find one.
-bool TextLayout::needsLayout(const jchar* text, jint len, jint bidiFlags) {
- if (bidiFlags == kBidi_Force_LTR) {
- return false;
- }
- if ((bidiFlags == kBidi_RTL) || (bidiFlags == kBidi_Default_RTL) ||
- bidiFlags == kBidi_Force_RTL) {
- return true;
- }
- for (int i = 0; i < len; ++i) {
- if (text[i] >= UNICODE_FIRST_RTL_CHAR) {
- return true;
- }
- }
- return false;
-}
-
-// Draws or gets the path of a paragraph of text on a single line, running bidi and shaping.
-// This will draw if canvas is not null, otherwise path must be non-null and it will create
-// a path representing the text that would have been drawn.
-void TextLayout::handleText(SkPaint *paint, const jchar* text, jsize len,
- jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
- sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
- text, 0, len, len, bidiFlags);
- if (value == NULL) {
- return ;
- }
- // Beware: this needs Glyph encoding (already done on the Paint constructor)
- paint->getTextPath(value->getGlyphs(), value->getGlyphsCount() * 2, x, y, path);
-}
-
-void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start,
- jint count, jint contextCount, jint dirFlags,
- jfloat* resultAdvances, jfloat* resultTotalAdvance) {
- sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
- chars, start, count, contextCount, dirFlags);
- if (value == NULL) {
- return ;
- }
- if (resultAdvances) {
- memcpy(resultAdvances, value->getAdvances(), value->getAdvancesCount() * sizeof(jfloat));
- }
- if (resultTotalAdvance) {
- *resultTotalAdvance = value->getTotalAdvance();
- }
-}
-
-void TextLayout::getTextPath(SkPaint *paint, const jchar *text, jsize len,
- jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
- handleText(paint, text, len, bidiFlags, x, y, path);
-}
-
-
-void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count,
- int bidiFlags, jfloat hOffset, jfloat vOffset,
- SkPath* path, SkCanvas* canvas) {
-
- sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
- text, 0, count, count, bidiFlags);
- if (value == NULL) {
- return;
- }
-
- // Beware: this needs Glyph encoding (already done on the Paint constructor)
- canvas->drawTextOnPathHV(value->getGlyphs(), value->getGlyphsCount() * 2, *path,
- hOffset, vOffset, *paint);
-}
-
-}
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
deleted file mode 100644
index 495d08a..0000000
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ /dev/null
@@ -1,948 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "TextLayoutCache"
-
-#include <utils/JenkinsHash.h>
-#include <utils/CallStack.h>
-
-#include "TextLayoutCache.h"
-#include "TextLayout.h"
-#include "SkGlyphCache.h"
-#include "SkTypeface_android.h"
-#include "HarfBuzzNGFaceSkia.h"
-#include <unicode/unistr.h>
-#include <unicode/uchar.h>
-#include <hb-icu.h>
-
-namespace android {
-
-//--------------------------------------------------------------------------------------------------
-
-ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine);
-
-//--------------------------------------------------------------------------------------------------
-
-TextLayoutCache::TextLayoutCache(TextLayoutShaper* shaper) :
- mShaper(shaper),
- mCache(LruCache<TextLayoutCacheKey, sp<TextLayoutValue> >::kUnlimitedCapacity),
- mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
- mCacheHitCount(0), mNanosecondsSaved(0) {
- init();
-}
-
-TextLayoutCache::~TextLayoutCache() {
- mCache.clear();
-}
-
-void TextLayoutCache::init() {
- mCache.setOnEntryRemovedListener(this);
-
- mDebugLevel = readRtlDebugLevel();
- mDebugEnabled = mDebugLevel & kRtlDebugCaches;
- ALOGD("Using debug level = %d - Debug Enabled = %d", mDebugLevel, mDebugEnabled);
-
- mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
-
- if (mDebugEnabled) {
- ALOGD("Initialization is done - Start time = %lld", mCacheStartTime);
- }
-
- mInitialized = true;
-}
-
-/**
- * Callbacks
- */
-void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutValue>& desc) {
- size_t totalSizeToDelete = text.getSize() + desc->getSize();
- mSize -= totalSizeToDelete;
- if (mDebugEnabled) {
- ALOGD("Cache value %p deleted, size = %zu", desc.get(), totalSizeToDelete);
- }
-}
-
-/*
- * Cache clearing
- */
-void TextLayoutCache::purgeCaches() {
- AutoMutex _l(mLock);
- mCache.clear();
- mShaper->purgeCaches();
-}
-
-/*
- * Caching
- */
-sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint,
- const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
- AutoMutex _l(mLock);
-#ifdef USE_MINIKIN
- // We want to get rid of all legacy calls in the Minikin case, so log
- ALOGW("TextLayoutCache being invoked!");
- CallStack _cs(LOG_TAG);
-#endif
- nsecs_t startTime = 0;
- if (mDebugEnabled) {
- startTime = systemTime(SYSTEM_TIME_MONOTONIC);
- }
-
- // Create the key
- TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
-
- // Get value from cache if possible
- sp<TextLayoutValue> value = mCache.get(key);
-
- // Value not found for the key, we need to add a new value in the cache
- if (value == NULL) {
- if (mDebugEnabled) {
- startTime = systemTime(SYSTEM_TIME_MONOTONIC);
- }
-
- value = new TextLayoutValue(contextCount);
-
- // Compute advances and store them
- mShaper->computeValues(value.get(), paint,
- reinterpret_cast<const UChar*>(key.getText()), start, count,
- size_t(contextCount), int(dirFlags));
-
- if (mDebugEnabled) {
- value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime);
- }
-
- // Don't bother to add in the cache if the entry is too big
- size_t size = key.getSize() + value->getSize();
- if (size <= mMaxSize) {
- // Cleanup to make some room if needed
- if (mSize + size > mMaxSize) {
- if (mDebugEnabled) {
- ALOGD("Need to clean some entries for making some room for a new entry");
- }
- while (mSize + size > mMaxSize) {
- // This will call the callback
- bool removedOne = mCache.removeOldest();
- LOG_ALWAYS_FATAL_IF(!removedOne, "The cache is non-empty but we "
- "failed to remove the oldest entry. "
- "mSize = %u, size = %zu, mMaxSize = %u, mCache.size() = %zu",
- mSize, size, mMaxSize, mCache.size());
- }
- }
-
- // Update current cache size
- mSize += size;
-
- bool putOne = mCache.put(key, value);
- LOG_ALWAYS_FATAL_IF(!putOne, "Failed to put an entry into the cache. "
- "This indicates that the cache already has an entry with the "
- "same key but it should not since we checked earlier!"
- " - start = %d, count = %d, contextCount = %d - Text = '%s'",
- start, count, contextCount, String8(key.getText() + start, count).string());
-
- if (mDebugEnabled) {
- nsecs_t totalTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
- ALOGD("CACHE MISS: Added entry %p "
- "with start = %d, count = %d, contextCount = %d, "
- "entry size %zu bytes, remaining space %d bytes"
- " - Compute time %0.6f ms - Put time %0.6f ms - Text = '%s'",
- value.get(), start, count, contextCount, size, mMaxSize - mSize,
- value->getElapsedTime() * 0.000001f,
- (totalTime - value->getElapsedTime()) * 0.000001f,
- String8(key.getText() + start, count).string());
- }
- } else {
- if (mDebugEnabled) {
- ALOGD("CACHE MISS: Calculated but not storing entry because it is too big "
- "with start = %d, count = %d, contextCount = %d, "
- "entry size %zu bytes, remaining space %d bytes"
- " - Compute time %0.6f ms - Text = '%s'",
- start, count, contextCount, size, mMaxSize - mSize,
- value->getElapsedTime() * 0.000001f,
- String8(key.getText() + start, count).string());
- }
- }
- } else {
- // This is a cache hit, just log timestamp and user infos
- if (mDebugEnabled) {
- nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
- mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet);
- ++mCacheHitCount;
-
- if (value->getElapsedTime() > 0) {
- float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
- / ((float)value->getElapsedTime()));
- ALOGD("CACHE HIT #%d with start = %d, count = %d, contextCount = %d"
- "- Compute time %0.6f ms - "
- "Cache get time %0.6f ms - Gain in percent: %2.2f - Text = '%s'",
- mCacheHitCount, start, count, contextCount,
- value->getElapsedTime() * 0.000001f,
- elapsedTimeThruCacheGet * 0.000001f,
- deltaPercent,
- String8(key.getText() + start, count).string());
- }
- if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
- dumpCacheStats();
- }
- }
- }
- return value;
-}
-
-void TextLayoutCache::dumpCacheStats() {
- float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
- float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
-
- size_t cacheSize = mCache.size();
-
- ALOGD("------------------------------------------------");
- ALOGD("Cache stats");
- ALOGD("------------------------------------------------");
- ALOGD("pid : %d", getpid());
- ALOGD("running : %.0f seconds", timeRunningInSec);
- ALOGD("entries : %zu", cacheSize);
- ALOGD("max size : %d bytes", mMaxSize);
- ALOGD("used : %d bytes according to mSize", mSize);
- ALOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
- ALOGD("hits : %d", mCacheHitCount);
- ALOGD("saved : %0.6f ms", mNanosecondsSaved * 0.000001f);
- ALOGD("------------------------------------------------");
-}
-
-/**
- * TextLayoutCacheKey
- */
-TextLayoutCacheKey::TextLayoutCacheKey(): start(0), count(0), contextCount(0),
- dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
- hinting(SkPaint::kNo_Hinting) {
- paintOpts.setUseFontFallbacks(true);
-}
-
-TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
- size_t start, size_t count, size_t contextCount, int dirFlags) :
- start(start), count(count), contextCount(contextCount),
- dirFlags(dirFlags) {
- textCopy.setTo(text, contextCount);
- typeface = paint->getTypeface();
- textSize = paint->getTextSize();
- textSkewX = paint->getTextSkewX();
- textScaleX = paint->getTextScaleX();
- flags = paint->getFlags();
- hinting = paint->getHinting();
- paintOpts = paint->getPaintOptionsAndroid();
-}
-
-TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
- textCopy(other.textCopy),
- start(other.start),
- count(other.count),
- contextCount(other.contextCount),
- dirFlags(other.dirFlags),
- typeface(other.typeface),
- textSize(other.textSize),
- textSkewX(other.textSkewX),
- textScaleX(other.textScaleX),
- flags(other.flags),
- hinting(other.hinting),
- paintOpts(other.paintOpts) {
-}
-
-int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
- int deltaInt = lhs.start - rhs.start;
- if (deltaInt != 0) return (deltaInt);
-
- deltaInt = lhs.count - rhs.count;
- if (deltaInt != 0) return (deltaInt);
-
- deltaInt = lhs.contextCount - rhs.contextCount;
- if (deltaInt != 0) return (deltaInt);
-
- if (lhs.typeface < rhs.typeface) return -1;
- if (lhs.typeface > rhs.typeface) return +1;
-
- if (lhs.textSize < rhs.textSize) return -1;
- if (lhs.textSize > rhs.textSize) return +1;
-
- if (lhs.textSkewX < rhs.textSkewX) return -1;
- if (lhs.textSkewX > rhs.textSkewX) return +1;
-
- if (lhs.textScaleX < rhs.textScaleX) return -1;
- if (lhs.textScaleX > rhs.textScaleX) return +1;
-
- deltaInt = lhs.flags - rhs.flags;
- if (deltaInt != 0) return (deltaInt);
-
- deltaInt = lhs.hinting - rhs.hinting;
- if (deltaInt != 0) return (deltaInt);
-
- deltaInt = lhs.dirFlags - rhs.dirFlags;
- if (deltaInt) return (deltaInt);
-
- if (lhs.paintOpts != rhs.paintOpts)
- return memcmp(&lhs.paintOpts, &rhs.paintOpts, sizeof(SkPaintOptionsAndroid));
-
- return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar));
-}
-
-size_t TextLayoutCacheKey::getSize() const {
- return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
-}
-
-hash_t TextLayoutCacheKey::hash() const {
- uint32_t hash = JenkinsHashMix(0, start);
- hash = JenkinsHashMix(hash, count);
- /* contextCount not needed because it's included in text, below */
- hash = JenkinsHashMix(hash, hash_type(typeface));
- hash = JenkinsHashMix(hash, hash_type(textSize));
- hash = JenkinsHashMix(hash, hash_type(textSkewX));
- hash = JenkinsHashMix(hash, hash_type(textScaleX));
- hash = JenkinsHashMix(hash, flags);
- hash = JenkinsHashMix(hash, hinting);
- hash = JenkinsHashMix(hash, paintOpts.getFontVariant());
- // Note: leaving out language is not problematic, as equality comparisons
- // are still valid - the only bad thing that could happen is collisions.
- hash = JenkinsHashMixShorts(hash, getText(), contextCount);
- return JenkinsHashWhiten(hash);
-}
-
-/**
- * TextLayoutCacheValue
- */
-TextLayoutValue::TextLayoutValue(size_t contextCount) :
- mTotalAdvance(0), mElapsedTime(0) {
- mBounds.setEmpty();
- // Give a hint for advances and glyphs vectors size
- mAdvances.setCapacity(contextCount);
- mGlyphs.setCapacity(contextCount);
- mPos.setCapacity(contextCount * 2);
-}
-
-size_t TextLayoutValue::getSize() const {
- return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() +
- sizeof(jchar) * mGlyphs.capacity() + sizeof(jfloat) * mPos.capacity();
-}
-
-void TextLayoutValue::setElapsedTime(uint32_t time) {
- mElapsedTime = time;
-}
-
-uint32_t TextLayoutValue::getElapsedTime() {
- return mElapsedTime;
-}
-
-TextLayoutShaper::TextLayoutShaper() {
- mBuffer = hb_buffer_create();
-}
-
-TextLayoutShaper::~TextLayoutShaper() {
- hb_buffer_destroy(mBuffer);
-}
-
-void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint,
- const UChar* chars, size_t start, size_t count, size_t contextCount, int dirFlags) {
- computeValues(paint, chars, start, count, contextCount, dirFlags,
- &value->mAdvances, &value->mTotalAdvance, &value->mBounds,
- &value->mGlyphs, &value->mPos);
-#if DEBUG_ADVANCES
- ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count,
- contextCount, value->mTotalAdvance);
-#endif
-}
-
-void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
- size_t start, size_t count, size_t contextCount, int dirFlags,
- Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, SkRect* outBounds,
- Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
- *outTotalAdvance = 0;
- if (!count) {
- return;
- }
-
- UBiDiLevel bidiReq = 0;
- bool forceLTR = false;
- bool forceRTL = false;
-
- switch (dirFlags & kBidi_Mask) {
- case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
- case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
- case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
- case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
- case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
- case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
- }
-
- bool useSingleRun = false;
- bool isRTL = forceRTL;
- if (forceLTR || forceRTL) {
- useSingleRun = true;
- } else {
- UBiDi* bidi = ubidi_open();
- if (bidi) {
- UErrorCode status = U_ZERO_ERROR;
-#if DEBUG_GLYPHS
- ALOGD("******** ComputeValues -- start");
- ALOGD(" -- string = '%s'", String8(chars + start, count).string());
- ALOGD(" -- start = %d", start);
- ALOGD(" -- count = %d", count);
- ALOGD(" -- contextCount = %d", contextCount);
- ALOGD(" -- bidiReq = %d", bidiReq);
-#endif
- ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
- if (U_SUCCESS(status)) {
- int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
- ssize_t rc = ubidi_countRuns(bidi, &status);
-#if DEBUG_GLYPHS
- ALOGD(" -- dirFlags = %d", dirFlags);
- ALOGD(" -- paraDir = %d", paraDir);
- ALOGD(" -- run-count = %d", int(rc));
-#endif
- if (U_SUCCESS(status) && rc == 1) {
- // Normal case: one run, status is ok
- isRTL = (paraDir == 1);
- useSingleRun = true;
- } else if (!U_SUCCESS(status) || rc < 1) {
- ALOGW("Need to force to single run -- string = '%s',"
- " status = %d, rc = %d",
- String8(chars + start, count).string(), status, int(rc));
- isRTL = (paraDir == 1);
- useSingleRun = true;
- } else {
- int32_t end = start + count;
- for (size_t i = 0; i < size_t(rc); ++i) {
- int32_t startRun = -1;
- int32_t lengthRun = -1;
- UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
-
- if (startRun == -1 || lengthRun == -1) {
- // Something went wrong when getting the visual run, need to clear
- // already computed data before doing a single run pass
- ALOGW("Visual run is not valid");
- outGlyphs->clear();
- outAdvances->clear();
- outPos->clear();
- *outTotalAdvance = 0;
- isRTL = (paraDir == 1);
- useSingleRun = true;
- break;
- }
-
- if (startRun >= end) {
- continue;
- }
- int32_t endRun = startRun + lengthRun;
- if (endRun <= int32_t(start)) {
- continue;
- }
- if (startRun < int32_t(start)) {
- startRun = int32_t(start);
- }
- if (endRun > end) {
- endRun = end;
- }
-
- lengthRun = endRun - startRun;
- isRTL = (runDir == UBIDI_RTL);
-#if DEBUG_GLYPHS
- ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
- i, startRun, lengthRun, isRTL);
-#endif
- computeRunValues(paint, chars, startRun, lengthRun, contextCount, isRTL,
- outAdvances, outTotalAdvance, outBounds, outGlyphs, outPos);
-
- }
- }
- } else {
- ALOGW("Cannot set Para");
- useSingleRun = true;
- isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
- }
- ubidi_close(bidi);
- } else {
- ALOGW("Cannot ubidi_open()");
- useSingleRun = true;
- isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
- }
- }
-
- // Default single run case
- if (useSingleRun){
-#if DEBUG_GLYPHS
- ALOGD("Using a SINGLE BiDi Run "
- "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL);
-#endif
- computeRunValues(paint, chars, start, count, contextCount, isRTL,
- outAdvances, outTotalAdvance, outBounds, outGlyphs, outPos);
- }
-
-#if DEBUG_GLYPHS
- ALOGD(" -- Total returned glyphs-count = %d", outGlyphs->size());
- ALOGD("******** ComputeValues -- end");
-#endif
-}
-
-#define HB_IsHighSurrogate(ucs) \
- (((ucs) & 0xfc00) == 0xd800)
-
-#define HB_IsLowSurrogate(ucs) \
- (((ucs) & 0xfc00) == 0xdc00)
-
-#ifndef HB_SurrogateToUcs4
-#define HB_SurrogateToUcs4(high, low) \
- (((hb_codepoint_t)(high))<<10) + (low) - 0x35fdc00;
-#endif
-
-#define HB_InvalidCodePoint ~0u
-
-hb_codepoint_t
-utf16_to_code_point(const uint16_t *chars, size_t len, ssize_t *iter) {
- const uint16_t v = chars[(*iter)++];
- if (HB_IsHighSurrogate(v)) {
- // surrogate pair
- if (size_t(*iter) >= len) {
- // the surrogate is incomplete.
- return HB_InvalidCodePoint;
- }
- const uint16_t v2 = chars[(*iter)++];
- if (!HB_IsLowSurrogate(v2)) {
- // invalidate surrogate pair.
- (*iter)--;
- return HB_InvalidCodePoint;
- }
-
- return HB_SurrogateToUcs4(v, v2);
- }
-
- if (HB_IsLowSurrogate(v)) {
- // this isn't a valid code point
- return HB_InvalidCodePoint;
- }
-
- return v;
-}
-
-hb_codepoint_t
-utf16_to_code_point_prev(const uint16_t *chars, size_t len, ssize_t *iter) {
- const uint16_t v = chars[(*iter)--];
- if (HB_IsLowSurrogate(v)) {
- // surrogate pair
- if (*iter < 0) {
- // the surrogate is incomplete.
- return HB_InvalidCodePoint;
- }
- const uint16_t v2 = chars[(*iter)--];
- if (!HB_IsHighSurrogate(v2)) {
- // invalidate surrogate pair.
- (*iter)++;
- return HB_InvalidCodePoint;
- }
-
- return HB_SurrogateToUcs4(v2, v);
- }
-
- if (HB_IsHighSurrogate(v)) {
- // this isn't a valid code point
- return HB_InvalidCodePoint;
- }
-
- return v;
-}
-
-struct ScriptRun {
- hb_script_t script;
- size_t pos;
- size_t length;
-};
-
-hb_script_t code_point_to_script(hb_codepoint_t codepoint) {
- static hb_unicode_funcs_t* u;
- if (!u) {
- u = hb_icu_get_unicode_funcs();
- }
- return hb_unicode_script(u, codepoint);
-}
-
-bool
-hb_utf16_script_run_next(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) {
- if (size_t(*iter) == len)
- return false;
-
- run->pos = *iter;
- const uint32_t init_cp = utf16_to_code_point(chars, len, iter);
- const hb_script_t init_script = code_point_to_script(init_cp);
- hb_script_t current_script = init_script;
- run->script = init_script;
-
- for (;;) {
- if (size_t(*iter) == len)
- break;
- const ssize_t prev_iter = *iter;
- const uint32_t cp = utf16_to_code_point(chars, len, iter);
- const hb_script_t script = code_point_to_script(cp);
-
- if (script != current_script) {
- /* BEGIN android-changed
- The condition was not correct by doing "a == b == constant"
- END android-changed */
- if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) {
- // If we started off as inherited, we take whatever we can find.
- run->script = script;
- current_script = script;
- continue;
- } else if (script == HB_SCRIPT_INHERITED) {
- continue;
- } else {
- *iter = prev_iter;
- break;
- }
- }
- }
-
- if (run->script == HB_SCRIPT_INHERITED)
- run->script = HB_SCRIPT_COMMON;
-
- run->length = *iter - run->pos;
- return true;
-}
-
-bool
-hb_utf16_script_run_prev(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) {
- if (*iter == -1)
- return false;
-
- const size_t ending_index = *iter;
- const uint32_t init_cp = utf16_to_code_point_prev(chars, len, iter);
- const hb_script_t init_script = code_point_to_script(init_cp);
- hb_script_t current_script = init_script;
- run->script = init_script;
- size_t break_iter = *iter;
-
- for (;;) {
- if (*iter < 0)
- break;
- const uint32_t cp = utf16_to_code_point_prev(chars, len, iter);
- const hb_script_t script = code_point_to_script(cp);
-
- if (script != current_script) {
- if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) {
- // If we started off as inherited, we take whatever we can find.
- run->script = script;
- current_script = script;
- // In cases of script1 + inherited + script2, always group the inherited
- // with script1.
- break_iter = *iter;
- continue;
- } else if (script == HB_SCRIPT_INHERITED) {
- continue;
- } else {
- *iter = break_iter;
- break;
- }
- } else {
- break_iter = *iter;
- }
- }
-
- if (run->script == HB_SCRIPT_INHERITED)
- run->script = HB_SCRIPT_COMMON;
-
- run->pos = *iter + 1;
- run->length = ending_index - *iter;
- return true;
-}
-
-
-static void logGlyphs(hb_buffer_t* buffer) {
- unsigned int numGlyphs;
- hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, &numGlyphs);
- hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer, NULL);
- ALOGD(" -- glyphs count=%d", numGlyphs);
- for (size_t i = 0; i < numGlyphs; i++) {
- ALOGD(" -- glyph[%d] = %d, cluster = %u, advance = %0.2f, offset.x = %0.2f, offset.y = %0.2f", i,
- info[i].codepoint,
- info[i].cluster,
- HBFixedToFloat(positions[i].x_advance),
- HBFixedToFloat(positions[i].x_offset),
- HBFixedToFloat(positions[i].y_offset));
- }
-}
-
-void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* contextChars,
- size_t start, size_t count, size_t contextCount, bool isRTL,
- Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, SkRect* outBounds,
- Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
- if (!count) {
- // We cannot shape an empty run.
- return;
- }
-
- // To be filled in later
- for (size_t i = 0; i < count; i++) {
- outAdvances->add(0);
- }
-
- // Set the string properties
- const UChar* chars = contextChars + start;
-
- // Define shaping paint properties
- mShapingPaint.setTextSize(paint->getTextSize());
- float skewX = paint->getTextSkewX();
- mShapingPaint.setTextSkewX(skewX);
- mShapingPaint.setTextScaleX(paint->getTextScaleX());
- mShapingPaint.setFlags(paint->getFlags());
- mShapingPaint.setHinting(paint->getHinting());
- mShapingPaint.setPaintOptionsAndroid(paint->getPaintOptionsAndroid());
-
- // Split the BiDi run into Script runs. Harfbuzz will populate the pos, length and script
- // into the shaperItem
- ssize_t indexFontRun = isRTL ? count - 1 : 0;
- jfloat totalAdvance = *outTotalAdvance;
- ScriptRun run; // relative to chars
- while ((isRTL) ?
- hb_utf16_script_run_prev(&run, chars, count, &indexFontRun):
- hb_utf16_script_run_next(&run, chars, count, &indexFontRun)) {
-
-#if DEBUG_GLYPHS
- ALOGD("-------- Start of Script Run --------");
- ALOGD("Shaping Script Run with");
- ALOGD(" -- isRTL = %d", isRTL);
- ALOGD(" -- HB script = %c%c%c%c", HB_UNTAG(run.script));
- ALOGD(" -- run.pos = %d", int(run.pos));
- ALOGD(" -- run.length = %d", int(run.length));
- ALOGD(" -- run = '%s'", String8(chars + run.pos, run.length).string());
- ALOGD(" -- string = '%s'", String8(chars, count).string());
-#endif
-
- hb_buffer_reset(mBuffer);
- // Note: if we want to set unicode functions, etc., this is the place.
-
- hb_buffer_set_direction(mBuffer, isRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
- hb_buffer_set_script(mBuffer, run.script);
- SkString langString = paint->getPaintOptionsAndroid().getLanguage().getTag();
- hb_buffer_set_language(mBuffer, hb_language_from_string(langString.c_str(), -1));
- hb_buffer_add_utf16(mBuffer, contextChars, contextCount, start + run.pos, run.length);
-
- // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs
- // and shape the Font run
- size_t glyphBaseCount = shapeFontRun(paint);
- unsigned int numGlyphs;
- hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs);
- hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(mBuffer, NULL);
-
-#if DEBUG_GLYPHS
- ALOGD("Got from Harfbuzz");
- ALOGD(" -- glyphBaseCount = %d", glyphBaseCount);
- ALOGD(" -- num_glyph = %d", numGlyphs);
- ALOGD(" -- isDevKernText = %d", paint->isDevKernText());
- ALOGD(" -- initial totalAdvance = %f", totalAdvance);
-
- logGlyphs(mBuffer);
-#endif
-
- for (size_t i = 0; i < numGlyphs; i++) {
- size_t cluster = info[i].cluster - start;
- float xAdvance = HBFixedToFloat(positions[i].x_advance);
- outAdvances->replaceAt(outAdvances->itemAt(cluster) + xAdvance, cluster);
- jchar glyphId = info[i].codepoint + glyphBaseCount;
- outGlyphs->add(glyphId);
- float xo = HBFixedToFloat(positions[i].x_offset);
- float yo = -HBFixedToFloat(positions[i].y_offset);
-
- float xpos = totalAdvance + xo + yo * skewX;
- float ypos = yo;
- outPos->add(xpos);
- outPos->add(ypos);
- totalAdvance += xAdvance;
-
- SkAutoGlyphCache autoCache(mShapingPaint, NULL, NULL);
- const SkGlyph& metrics = autoCache.getCache()->getGlyphIDMetrics(glyphId);
- outBounds->join(xpos + metrics.fLeft, ypos + metrics.fTop,
- xpos + metrics.fLeft + metrics.fWidth, ypos + metrics.fTop + metrics.fHeight);
-
- }
- }
-
- *outTotalAdvance = totalAdvance;
-
-#if DEBUG_GLYPHS
- ALOGD(" -- final totalAdvance = %f", totalAdvance);
- ALOGD("-------- End of Script Run --------");
-#endif
-}
-
-/**
- * Return the first typeface in the logical change, starting with this typeface,
- * that contains the specified unichar, or NULL if none is found.
- */
-SkTypeface* TextLayoutShaper::typefaceForScript(const SkPaint* paint, SkTypeface* typeface,
- hb_script_t script) {
- SkTypeface::Style currentStyle = SkTypeface::kNormal;
- if (typeface) {
- currentStyle = typeface->style();
- }
- typeface = SkCreateTypefaceForScript(script, currentStyle);
-#if DEBUG_GLYPHS
- ALOGD("Using Harfbuzz Script %c%c%c%c, Style %d", HB_UNTAG(script), currentStyle);
-#endif
- return typeface;
-}
-
-bool TextLayoutShaper::isComplexScript(hb_script_t script) {
- switch (script) {
- case HB_SCRIPT_COMMON:
- case HB_SCRIPT_GREEK:
- case HB_SCRIPT_CYRILLIC:
- case HB_SCRIPT_HANGUL:
- case HB_SCRIPT_INHERITED:
- case HB_SCRIPT_HAN:
- case HB_SCRIPT_KATAKANA:
- case HB_SCRIPT_HIRAGANA:
- return false;
- default:
- return true;
- }
-}
-
-size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint) {
- // Update Harfbuzz Shaper
-
- SkTypeface* typeface = paint->getTypeface();
-
- // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz
- // This is needed as the Typeface used for shaping can be not the default one
- // when we are shaping any script that needs to use a fallback Font.
- // If we are a "common" script we dont need to shift
- size_t baseGlyphCount = 0;
- hb_codepoint_t firstUnichar = 0;
- if (isComplexScript(hb_buffer_get_script(mBuffer))) {
- unsigned int numGlyphs;
- hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs);
- for (size_t i = 0; i < numGlyphs; i++) {
- firstUnichar = info[i].codepoint;
- if (firstUnichar != ' ') {
- break;
- }
- }
- baseGlyphCount = paint->getBaseGlyphCount(firstUnichar);
- }
-
- SkTypeface* scriptTypeface = NULL;
- if (baseGlyphCount != 0) {
- scriptTypeface = typefaceForScript(paint, typeface,
- hb_buffer_get_script(mBuffer));
-#if DEBUG_GLYPHS
- ALOGD("Using Default Typeface for script %c%c%c%c",
- HB_UNTAG(hb_buffer_get_script(mBuffer)));
-#endif
- }
- if (scriptTypeface) {
- typeface = scriptTypeface;
- } else {
- baseGlyphCount = 0;
- if (typeface) {
- SkSafeRef(typeface);
- } else {
- typeface = SkTypeface::CreateFromName(NULL, SkTypeface::kNormal);
-#if DEBUG_GLYPHS
- ALOGD("Using Default Typeface (normal style)");
-#endif
- }
- }
-
- mShapingPaint.setTypeface(typeface);
- hb_face_t* face = referenceCachedHBFace(typeface);
-
- float sizeY = paint->getTextSize();
- float sizeX = sizeY * paint->getTextScaleX();
- hb_font_t* font = createFont(face, &mShapingPaint, sizeX, sizeY);
- hb_face_destroy(face);
-
-#if DEBUG_GLYPHS
- ALOGD("Run typeface = %p, uniqueID = %d, face = %p",
- typeface, typeface->uniqueID(), face);
-#endif
- SkSafeUnref(typeface);
-
- hb_shape(font, mBuffer, NULL, 0);
- hb_font_destroy(font);
-
- mShapingPaint.setTypeface(paint->getTypeface());
- return baseGlyphCount;
-}
-
-hb_face_t* TextLayoutShaper::referenceCachedHBFace(SkTypeface* typeface) {
- SkFontID fontId = typeface->uniqueID();
- ssize_t index = mCachedHBFaces.indexOfKey(fontId);
- if (index >= 0) {
- return hb_face_reference(mCachedHBFaces.valueAt(index));
- }
- // TODO: destroy function
- hb_face_t* face = hb_face_create_for_tables(harfbuzzSkiaReferenceTable, typeface, NULL);
-#if DEBUG_GLYPHS
- ALOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface);
-#endif
- mCachedHBFaces.add(fontId, face);
- return hb_face_reference(face);
-}
-
-void TextLayoutShaper::purgeCaches() {
- size_t cacheSize = mCachedHBFaces.size();
- for (size_t i = 0; i < cacheSize; i++) {
- hb_face_destroy(mCachedHBFaces.valueAt(i));
- }
- mCachedHBFaces.clear();
-}
-
-TextLayoutEngine::TextLayoutEngine() {
- mShaper = new TextLayoutShaper();
-#if USE_TEXT_LAYOUT_CACHE
- mTextLayoutCache = new TextLayoutCache(mShaper);
-#else
- mTextLayoutCache = NULL;
-#endif
-}
-
-TextLayoutEngine::~TextLayoutEngine() {
- delete mTextLayoutCache;
- delete mShaper;
-}
-
-sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text,
- jint start, jint count, jint contextCount, jint dirFlags) {
- sp<TextLayoutValue> value;
-#if USE_TEXT_LAYOUT_CACHE
- value = mTextLayoutCache->getValue(paint, text, start, count,
- contextCount, dirFlags);
- if (value == NULL) {
- ALOGE("Cannot get TextLayoutCache value for text = '%s'",
- String8(text + start, count).string());
- }
-#else
- value = new TextLayoutValue(count);
- mShaper->computeValues(value.get(), paint,
- reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags);
-#endif
- return value;
-}
-
-void TextLayoutEngine::purgeCaches() {
-#if USE_TEXT_LAYOUT_CACHE
- mTextLayoutCache->purgeCaches();
-#if DEBUG_GLYPHS
- ALOGD("Purged TextLayoutEngine caches");
-#endif
-#endif
-}
-
-
-} // namespace android
diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h
deleted file mode 100644
index 54704ec..0000000
--- a/core/jni/android/graphics/TextLayoutCache.h
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 ANDROID_TEXT_LAYOUT_CACHE_H
-#define ANDROID_TEXT_LAYOUT_CACHE_H
-
-#include "RtlProperties.h"
-
-#include <stddef.h>
-#include <utils/threads.h>
-#include <utils/String16.h>
-#include <utils/LruCache.h>
-#include <utils/KeyedVector.h>
-#include <utils/RefBase.h>
-#include <utils/Singleton.h>
-
-#include <SkAutoKern.h>
-#include <SkPaint.h>
-#include <SkTemplates.h>
-#include <SkTypeface.h>
-#include <SkUtils.h>
-
-#include <unicode/ubidi.h>
-#include <unicode/unistr.h>
-
-#include <hb.h>
-
-#include <android_runtime/AndroidRuntime.h>
-
-#define UNICODE_NOT_A_CHAR 0xffff
-#define UNICODE_ZWSP 0x200b
-#define UNICODE_FIRST_LOW_SURROGATE 0xdc00
-#define UNICODE_FIRST_HIGH_SURROGATE 0xd800
-#define UNICODE_FIRST_PRIVATE_USE 0xe000
-#define UNICODE_FIRST_RTL_CHAR 0x0590
-
-// Temporary buffer size
-#define CHAR_BUFFER_SIZE 80
-
-// Converts a number of mega-bytes into bytes
-#define MB(s) s * 1024 * 1024
-
-// Define the default cache size in Mb
-#define DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB 0.500f
-
-// Define the interval in number of cache hits between two statistics dump
-#define DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL 100
-
-namespace android {
-
-/**
- * TextLayoutCacheKey is the Cache key
- */
-class TextLayoutCacheKey {
-public:
- TextLayoutCacheKey();
-
- TextLayoutCacheKey(const SkPaint* paint, const UChar* text, size_t start, size_t count,
- size_t contextCount, int dirFlags);
-
- TextLayoutCacheKey(const TextLayoutCacheKey& other);
-
- /**
- * Get the size of the Cache key.
- */
- size_t getSize() const;
-
- static int compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs);
-
- inline const UChar* getText() const { return textCopy.string(); }
-
- bool operator==(const TextLayoutCacheKey& other) const {
- return compare(*this, other) == 0;
- }
-
- bool operator!=(const TextLayoutCacheKey& other) const {
- return compare(*this, other) != 0;
- }
-
- hash_t hash() const;
-private:
- String16 textCopy;
- size_t start;
- size_t count;
- size_t contextCount;
- int dirFlags;
- SkTypeface* typeface;
- SkScalar textSize;
- SkScalar textSkewX;
- SkScalar textScaleX;
- uint32_t flags;
- SkPaint::Hinting hinting;
- SkPaintOptionsAndroid paintOpts;
-
-}; // TextLayoutCacheKey
-
-inline int strictly_order_type(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
- return TextLayoutCacheKey::compare(lhs, rhs) < 0;
-}
-
-inline int compare_type(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
- return TextLayoutCacheKey::compare(lhs, rhs);
-}
-
-inline hash_t hash_type(const TextLayoutCacheKey& key) {
- return key.hash();
-}
-
-/*
- * TextLayoutValue is the Cache value
- */
-class TextLayoutValue : public RefBase {
-public:
- TextLayoutValue(size_t contextCount);
-
- void setElapsedTime(uint32_t time);
- uint32_t getElapsedTime();
-
- inline const jfloat* getAdvances() const { return mAdvances.array(); }
- inline size_t getAdvancesCount() const { return mAdvances.size(); }
- inline jfloat getTotalAdvance() const { return mTotalAdvance; }
- inline const SkRect& getBounds() const { return mBounds; }
- inline const jchar* getGlyphs() const { return mGlyphs.array(); }
- inline size_t getGlyphsCount() const { return mGlyphs.size(); }
- inline const jfloat* getPos() const { return mPos.array(); }
- inline size_t getPosCount() const { return mPos.size(); }
-
- /**
- * Advances vector
- */
- Vector<jfloat> mAdvances;
-
- /**
- * Total number of advances
- */
- jfloat mTotalAdvance;
-
- /**
- * Bounds containing all glyphs
- */
- SkRect mBounds;
-
- /**
- * Glyphs vector
- */
- Vector<jchar> mGlyphs;
-
- /**
- * Pos vector (2 * i is x pos, 2 * i + 1 is y pos, same as drawPosText)
- */
- Vector<jfloat> mPos;
-
- /**
- * Get the size of the Cache entry
- */
- size_t getSize() const;
-
-private:
- /**
- * Time for computing the values (in milliseconds)
- */
- uint32_t mElapsedTime;
-
-}; // TextLayoutCacheValue
-
-/**
- * The TextLayoutShaper is responsible for shaping (with the Harfbuzz library)
- */
-class TextLayoutShaper {
-public:
- TextLayoutShaper();
- virtual ~TextLayoutShaper();
-
- void computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars,
- size_t start, size_t count, size_t contextCount, int dirFlags);
-
- void purgeCaches();
-
-private:
- /**
- * Harfbuzz buffer for shaping
- */
- hb_buffer_t* mBuffer;
-
- /**
- * Skia Paint used for shaping
- */
- SkPaint mShapingPaint;
-
- /**
- * Cache of Harfbuzz faces
- */
- KeyedVector<SkFontID, hb_face_t*> mCachedHBFaces;
-
- SkTypeface* typefaceForScript(const SkPaint* paint, SkTypeface* typeface,
- hb_script_t script);
-
- size_t shapeFontRun(const SkPaint* paint);
-
- void computeValues(const SkPaint* paint, const UChar* chars,
- size_t start, size_t count, size_t contextCount, int dirFlags,
- Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, SkRect* outBounds,
- Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos);
-
- void computeRunValues(const SkPaint* paint, const UChar* chars,
- size_t start, size_t count, size_t contextCount, bool isRTL,
- Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, SkRect* outBounds,
- Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos);
-
- SkTypeface* setCachedTypeface(SkTypeface** typeface, hb_script_t script, SkTypeface::Style style);
- hb_face_t* referenceCachedHBFace(SkTypeface* typeface);
-
- bool isComplexScript(hb_script_t script);
-}; // TextLayoutShaper
-
-/**
- * Cache of text layout information.
- */
-class TextLayoutCache : private OnEntryRemoved<TextLayoutCacheKey, sp<TextLayoutValue> >
-{
-public:
- TextLayoutCache(TextLayoutShaper* shaper);
-
- ~TextLayoutCache();
-
- bool isInitialized() {
- return mInitialized;
- }
-
- /**
- * Used as a callback when an entry is removed from the cache
- * Do not invoke directly
- */
- void operator()(TextLayoutCacheKey& text, sp<TextLayoutValue>& desc);
-
- sp<TextLayoutValue> getValue(const SkPaint* paint, const jchar* text, jint start,
- jint count, jint contextCount, jint dirFlags);
-
- /**
- * Clear the cache
- */
- void purgeCaches();
-
-private:
- TextLayoutShaper* mShaper;
- Mutex mLock;
- bool mInitialized;
-
- LruCache<TextLayoutCacheKey, sp<TextLayoutValue> > mCache;
-
- uint32_t mSize;
- uint32_t mMaxSize;
-
- uint32_t mCacheHitCount;
- uint64_t mNanosecondsSaved;
-
- uint64_t mCacheStartTime;
-
- RtlDebugLevel mDebugLevel;
- bool mDebugEnabled;
-
- /*
- * Class initialization
- */
- void init();
-
- /**
- * Dump Cache statistics
- */
- void dumpCacheStats();
-
-}; // TextLayoutCache
-
-/**
- * The TextLayoutEngine is reponsible for computing TextLayoutValues
- */
-class TextLayoutEngine : public Singleton<TextLayoutEngine> {
-public:
- TextLayoutEngine();
- virtual ~TextLayoutEngine();
-
- /**
- * Note: this method currently does a defensive copy of the text argument, in case
- * there is concurrent mutation of it. The contract may change, and may in the
- * future require the caller to guarantee that the contents will not change during
- * the call. Be careful of this when doing optimization.
- **/
- sp<TextLayoutValue> getValue(const SkPaint* paint, const jchar* text, jint start,
- jint count, jint contextCount, jint dirFlags);
-
- void purgeCaches();
-
-private:
- TextLayoutCache* mTextLayoutCache;
- TextLayoutShaper* mShaper;
-}; // TextLayoutEngine
-
-} // namespace android
-#endif /* ANDROID_TEXT_LAYOUT_CACHE_H */
-
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index b20c246..cf4e838 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -27,44 +27,10 @@
using namespace android;
-class AutoJavaStringToUTF8 {
-public:
- AutoJavaStringToUTF8(JNIEnv* env, jstring str) : fEnv(env), fJStr(str)
- {
- fCStr = env->GetStringUTFChars(str, NULL);
- }
- ~AutoJavaStringToUTF8()
- {
- fEnv->ReleaseStringUTFChars(fJStr, fCStr);
- }
- const char* c_str() const { return fCStr; }
-
-private:
- JNIEnv* fEnv;
- jstring fJStr;
- const char* fCStr;
-};
-
-static jlong Typeface_create(JNIEnv* env, jobject, jstring name,
- jint styleHandle) {
- SkTypeface::Style style = static_cast<SkTypeface::Style>(styleHandle);
- TypefaceImpl* face = NULL;
-
- if (NULL != name) {
- AutoJavaStringToUTF8 str(env, name);
- face = TypefaceImpl_createFromName(str.c_str(), style);
- }
-
- // return the default font at the best style if no exact match exists
- if (NULL == face) {
- face = TypefaceImpl_createFromName(NULL, style);
- }
- return reinterpret_cast<jlong>(face);
-}
-
static jlong Typeface_createFromTypeface(JNIEnv* env, jobject, jlong familyHandle, jint style) {
TypefaceImpl* family = reinterpret_cast<TypefaceImpl*>(familyHandle);
TypefaceImpl* face = TypefaceImpl_createFromTypeface(family, (SkTypeface::Style)style);
+ // TODO: the following logic shouldn't be necessary, the above should always succeed.
// Try to find the closest matching font, using the standard heuristic
if (NULL == face) {
face = TypefaceImpl_createFromTypeface(family, (SkTypeface::Style)(style ^ SkTypeface::kItalic));
@@ -72,9 +38,6 @@
for (int i = 0; NULL == face && i < 4; i++) {
face = TypefaceImpl_createFromTypeface(family, (SkTypeface::Style)i);
}
- if (NULL == face) {
- face = TypefaceImpl_createFromName(NULL, (SkTypeface::Style)style);
- }
return reinterpret_cast<jlong>(face);
}
@@ -88,33 +51,6 @@
return TypefaceImpl_getStyle(face);
}
-static jlong Typeface_createFromAsset(JNIEnv* env, jobject,
- jobject jassetMgr,
- jstring jpath) {
- NPE_CHECK_RETURN_ZERO(env, jassetMgr);
- NPE_CHECK_RETURN_ZERO(env, jpath);
-
- AssetManager* mgr = assetManagerForJavaObject(env, jassetMgr);
- if (NULL == mgr) {
- return NULL;
- }
-
- AutoJavaStringToUTF8 str(env, jpath);
- Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
- if (NULL == asset) {
- return NULL;
- }
-
- return reinterpret_cast<jlong>(TypefaceImpl_createFromAsset(asset));
-}
-
-static jlong Typeface_createFromFile(JNIEnv* env, jobject, jstring jpath) {
- NPE_CHECK_RETURN_ZERO(env, jpath);
-
- AutoJavaStringToUTF8 str(env, jpath);
- return reinterpret_cast<jlong>(TypefaceImpl_createFromFile(str.c_str()));
-}
-
static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray) {
ScopedLongArrayRO families(env, familyArray);
return reinterpret_cast<jlong>(TypefaceImpl_createFromFamilies(families.get(), families.size()));
@@ -128,14 +64,9 @@
///////////////////////////////////////////////////////////////////////////////
static JNINativeMethod gTypefaceMethods[] = {
- { "nativeCreate", "(Ljava/lang/String;I)J", (void*)Typeface_create },
{ "nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface },
{ "nativeUnref", "(J)V", (void*)Typeface_unref },
{ "nativeGetStyle", "(J)I", (void*)Typeface_getStyle },
- { "nativeCreateFromAsset", "(Landroid/content/res/AssetManager;Ljava/lang/String;)J",
- (void*)Typeface_createFromAsset },
- { "nativeCreateFromFile", "(Ljava/lang/String;)J",
- (void*)Typeface_createFromFile },
{ "nativeCreateFromArray", "([J)J",
(void*)Typeface_createFromArray },
{ "nativeSetDefault", "(J)V", (void*)Typeface_setDefault },
diff --git a/core/jni/android/graphics/TypefaceImpl.cpp b/core/jni/android/graphics/TypefaceImpl.cpp
index 1800d0c..7767b8d 100644
--- a/core/jni/android/graphics/TypefaceImpl.cpp
+++ b/core/jni/android/graphics/TypefaceImpl.cpp
@@ -27,22 +27,18 @@
#include "SkStream.h"
#include "SkTypeface.h"
-#ifdef USE_MINIKIN
#include <vector>
#include <minikin/FontCollection.h>
#include <minikin/FontFamily.h>
#include <minikin/Layout.h>
#include "SkPaint.h"
#include "MinikinSkia.h"
-#endif
#include "TypefaceImpl.h"
#include "Utils.h"
namespace android {
-#ifdef USE_MINIKIN
-
// Any weight greater than or equal to this is considered "bold" for
// legacy API.
static const int kBoldThreshold = 6;
@@ -135,30 +131,6 @@
return result;
}
-// Delete when removing USE_MINIKIN ifdef
-TypefaceImpl* TypefaceImpl_createFromName(const char* name, SkTypeface::Style style) {
- SkTypeface* face = SkTypeface::CreateFromName(name, style);
- return createFromSkTypeface(face);
-}
-
-// Delete when removing USE_MINIKIN ifdef
-TypefaceImpl* TypefaceImpl_createFromFile(const char* filename) {
- SkTypeface* face = SkTypeface::CreateFromFile(filename);
- return createFromSkTypeface(face);
-}
-
-// Delete when removing USE_MINIKIN ifdef
-TypefaceImpl* TypefaceImpl_createFromAsset(Asset* asset) {
- SkStream* stream = new AssetStreamAdaptor(asset,
- AssetStreamAdaptor::kYes_OwnAsset,
- AssetStreamAdaptor::kYes_HasMemoryBase);
- SkTypeface* face = SkTypeface::CreateFromStream(stream);
- // SkTypeFace::CreateFromStream calls ref() on the stream, so we
- // need to unref it here or it won't be freed later on
- stream->unref();
- return createFromSkTypeface(face);
-}
-
TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size) {
std::vector<FontFamily *>familyVec;
for (size_t i = 0; i < size; i++) {
@@ -202,52 +174,4 @@
gDefaultTypeface = face;
}
-#else // USE_MINIKIN
-
-/* Just use SkTypeface instead. */
-
-typedef SkTypeface TypefaceImpl;
-
-TypefaceImpl* TypefaceImpl_createFromTypeface(TypefaceImpl* src, SkTypeface::Style style) {
- return SkTypeface::CreateFromTypeface(src, style);
-}
-
-TypefaceImpl* TypefaceImpl_createFromName(const char* name, SkTypeface::Style style) {
- return SkTypeface::CreateFromName(name, style);
-}
-
-TypefaceImpl* TypefaceImpl_createFromFile(const char* filename) {
- return SkTypeface::CreateFromFile(filename);
-}
-
-TypefaceImpl* TypefaceImpl_createFromAsset(Asset* asset) {
- SkStream* stream = new AssetStreamAdaptor(asset,
- AssetStreamAdaptor::kYes_OwnAsset,
- AssetStreamAdaptor::kYes_HasMemoryBase);
- SkTypeface* face = SkTypeface::CreateFromStream(stream);
- // SkTypeFace::CreateFromStream calls ref() on the stream, so we
- // need to unref it here or it won't be freed later on
- stream->unref();
-
- return face;
-}
-
-TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size) {
- // Should never be called in non-Minikin builds
- return 0;
-}
-
-void TypefaceImpl_unref(TypefaceImpl* face) {
- SkSafeUnref(face);
-}
-
-int TypefaceImpl_getStyle(TypefaceImpl* face) {
- return face->style();
-}
-
-void TypefaceImpl_setDefault(TypefaceImpl* face) {
-}
-
-#endif // USE_MINIKIN
-
}
diff --git a/core/jni/android/graphics/TypefaceImpl.h b/core/jni/android/graphics/TypefaceImpl.h
index 4e92bce..12b3403 100644
--- a/core/jni/android/graphics/TypefaceImpl.h
+++ b/core/jni/android/graphics/TypefaceImpl.h
@@ -22,13 +22,10 @@
#include "SkTypeface.h"
#include <androidfw/AssetManager.h>
-#ifdef USE_MINIKIN
#include <minikin/FontCollection.h>
-#endif
namespace android {
-#ifdef USE_MINIKIN
struct TypefaceImpl {
FontCollection *fFontCollection;
FontStyle fStyle;
@@ -41,18 +38,9 @@
// TODO: when #ifdef USE_MINIKIN is removed, move to member functions.
TypefaceImpl* TypefaceImpl_resolveDefault(TypefaceImpl* src);
-#else
-typedef SkTypeface TypefaceImpl;
-#endif
TypefaceImpl* TypefaceImpl_createFromTypeface(TypefaceImpl* src, SkTypeface::Style style);
-TypefaceImpl* TypefaceImpl_createFromName(const char* name, SkTypeface::Style style);
-
-TypefaceImpl* TypefaceImpl_createFromFile(const char* filename);
-
-TypefaceImpl* TypefaceImpl_createFromAsset(Asset* asset);
-
// When we remove the USE_MINIKIN ifdef, probably a good idea to move the casting
// (from jlong to FontFamily*) to the caller in Typeface.cpp.
TypefaceImpl* TypefaceImpl_createFromFamilies(const jlong* families, size_t size);
diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
index 0048426..2f24a69 100644
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
@@ -15,6 +15,7 @@
*/
#define LOG_TAG "Legacy-CameraDevice-JNI"
+// #define LOG_NDEBUG 0
#include <utils/Log.h>
#include <utils/Errors.h>
#include <utils/Trace.h>
@@ -26,12 +27,14 @@
#include <ui/GraphicBuffer.h>
#include <system/window.h>
+#include <hardware/camera3.h>
using namespace android;
// fully-qualified class name
#define CAMERA_DEVICE_CLASS_NAME "android/hardware/camera2/legacy/LegacyCameraDevice"
#define CAMERA_DEVICE_BUFFER_SLACK 3
+#define DONT_CARE 0
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(*(a)))
@@ -118,8 +121,9 @@
return err;
}
- ALOGV("%s: Setting buffer count to %d", __FUNCTION__,
- maxBufferSlack + 1 + minUndequeuedBuffers);
+ ALOGV("%s: Setting buffer count to %d, size to (%dx%d), fmt (0x%x)", __FUNCTION__,
+ maxBufferSlack + 1 + minUndequeuedBuffers,
+ width, height, pixelFmt);
err = native_window_set_buffer_count(anw.get(), maxBufferSlack + 1 + minUndequeuedBuffers);
if (err != NO_ERROR) {
ALOGE("%s: Failed to set native window buffer count, error %s (%d).", __FUNCTION__,
@@ -148,11 +152,29 @@
int32_t width, // Width of the pixelBuffer
int32_t height, // Height of the pixelBuffer
int32_t pixelFmt, // Format of the pixelBuffer
- int64_t bufSize) {
+ int32_t bufSize) {
ATRACE_CALL();
status_t err = NO_ERROR;
ANativeWindowBuffer* anb;
- ALOGV("%s: Dequeue buffer from %p",__FUNCTION__, anw.get());
+ ALOGV("%s: Dequeue buffer from %p %dx%d (fmt=%x, size=%x)",
+ __FUNCTION__, anw.get(), width, height, pixelFmt, bufSize);
+
+ if (anw == 0) {
+ ALOGE("%s: anw must not be NULL", __FUNCTION__);
+ return BAD_VALUE;
+ } else if (pixelBuffer == NULL) {
+ ALOGE("%s: pixelBuffer must not be NULL", __FUNCTION__);
+ return BAD_VALUE;
+ } else if (width < 0) {
+ ALOGE("%s: width must be non-negative", __FUNCTION__);
+ return BAD_VALUE;
+ } else if (height < 0) {
+ ALOGE("%s: height must be non-negative", __FUNCTION__);
+ return BAD_VALUE;
+ } else if (bufSize < 0) {
+ ALOGE("%s: bufSize must be non-negative", __FUNCTION__);
+ return BAD_VALUE;
+ }
if (width < 0 || height < 0 || bufSize < 0) {
ALOGE("%s: Illegal argument, negative dimension passed to produceFrame", __FUNCTION__);
@@ -163,6 +185,8 @@
err = native_window_dequeue_buffer_and_wait(anw.get(), &anb);
if (err != NO_ERROR) return err;
+ // TODO: check anb is large enough to store the results
+
sp<GraphicBuffer> buf(new GraphicBuffer(anb, /*keepOwnership*/false));
switch(pixelFmt) {
@@ -257,7 +281,12 @@
err);
return err;
}
+ struct camera3_jpeg_blob footer = {
+ jpeg_blob_id: CAMERA3_JPEG_BLOB_ID,
+ jpeg_size: (uint32_t)width
+ };
memcpy(img, pixelBuffer, width);
+ memcpy(img + anb->width - sizeof(footer), &footer, sizeof(footer));
break;
}
default: {
@@ -308,71 +337,72 @@
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return 0;
+ return BAD_VALUE;
}
int32_t fmt = 0;
status_t err = anw->query(anw.get(), NATIVE_WINDOW_FORMAT, &fmt);
if(err != NO_ERROR) {
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "Error while querying surface pixel format (error code %d)", err);
- return 0;
+ ALOGE("%s: Error while querying surface pixel format %s (%d).", __FUNCTION__, strerror(-err),
+ err);
+ return err;
}
return fmt;
}
-static void LegacyCameraDevice_nativeDetectSurfaceDimens(JNIEnv* env, jobject thiz,
+static jint LegacyCameraDevice_nativeDetectSurfaceDimens(JNIEnv* env, jobject thiz,
jobject surface, jintArray dimens) {
ALOGV("nativeGetSurfaceDimens");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return;
+ return BAD_VALUE;
}
int32_t dimenBuf[2];
status_t err = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, dimenBuf);
if(err != NO_ERROR) {
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "Error while querying surface width (error code %d)", err);
- return;
+ ALOGE("%s: Error while querying surface width %s (%d).", __FUNCTION__, strerror(-err),
+ err);
+ return err;
}
err = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, dimenBuf + 1);
if(err != NO_ERROR) {
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "Error while querying surface height (error code %d)", err);
- return;
+ ALOGE("%s: Error while querying surface height %s (%d).", __FUNCTION__, strerror(-err),
+ err);
+ return err;
}
env->SetIntArrayRegion(dimens, /*start*/0, /*length*/ARRAY_SIZE(dimenBuf), dimenBuf);
+ return NO_ERROR;
}
-static void LegacyCameraDevice_nativeConfigureSurface(JNIEnv* env, jobject thiz, jobject surface,
+static jint LegacyCameraDevice_nativeConfigureSurface(JNIEnv* env, jobject thiz, jobject surface,
jint width, jint height, jint pixelFormat) {
ALOGV("nativeConfigureSurface");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return;
+ return BAD_VALUE;
}
status_t err = configureSurface(anw, width, height, pixelFormat, CAMERA_DEVICE_BUFFER_SLACK);
if (err != NO_ERROR) {
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "Error while producing frame (error code %d)", err);
- return;
+ ALOGE("%s: Error while configuring surface %s (%d).", __FUNCTION__, strerror(-err), err);
+ return err;
}
+ return NO_ERROR;
}
-static void LegacyCameraDevice_nativeProduceFrame(JNIEnv* env, jobject thiz, jobject surface,
+static jint LegacyCameraDevice_nativeProduceFrame(JNIEnv* env, jobject thiz, jobject surface,
jbyteArray pixelBuffer, jint width, jint height, jint pixelFormat) {
ALOGV("nativeProduceFrame");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return;
+ return BAD_VALUE;
}
if (pixelBuffer == NULL) {
jniThrowNullPointerException(env, "pixelBuffer");
- return;
+ return DONT_CARE;
}
int32_t bufSize = static_cast<int32_t>(env->GetArrayLength(pixelBuffer));
@@ -380,7 +410,7 @@
if (pixels == NULL) {
jniThrowNullPointerException(env, "pixels");
- return;
+ return DONT_CARE;
}
status_t err = produceFrame(anw, reinterpret_cast<uint8_t*>(pixels), width, height,
@@ -388,42 +418,42 @@
env->ReleaseByteArrayElements(pixelBuffer, pixels, JNI_ABORT);
if (err != NO_ERROR) {
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "Error while producing frame (error code %d)", err);
- return;
+ ALOGE("%s: Error while producing frame %s (%d).", __FUNCTION__, strerror(-err), err);
+ return err;
}
+ return NO_ERROR;
}
-static void LegacyCameraDevice_nativeSetSurfaceFormat(JNIEnv* env, jobject thiz, jobject surface,
+static jint LegacyCameraDevice_nativeSetSurfaceFormat(JNIEnv* env, jobject thiz, jobject surface,
jint pixelFormat) {
ALOGV("nativeSetSurfaceType");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return;
+ return BAD_VALUE;
}
status_t err = native_window_set_buffers_format(anw.get(), pixelFormat);
if (err != NO_ERROR) {
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "Error while setting surface format (error code %d)", err);
- return;
+ ALOGE("%s: Error while setting surface format %s (%d).", __FUNCTION__, strerror(-err), err);
+ return err;
}
+ return NO_ERROR;
}
-static void LegacyCameraDevice_nativeSetSurfaceDimens(JNIEnv* env, jobject thiz, jobject surface,
+static jint LegacyCameraDevice_nativeSetSurfaceDimens(JNIEnv* env, jobject thiz, jobject surface,
jint width, jint height) {
ALOGV("nativeSetSurfaceDimens");
sp<ANativeWindow> anw;
if ((anw = getNativeWindow(env, surface)) == NULL) {
ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__);
- return;
+ return BAD_VALUE;
}
status_t err = native_window_set_buffers_dimensions(anw.get(), width, height);
if (err != NO_ERROR) {
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "Error while setting surface format (error code %d)", err);
- return;
+ ALOGE("%s: Error while setting surface dimens %s (%d).", __FUNCTION__, strerror(-err), err);
+ return err;
}
+ return NO_ERROR;
}
} // extern "C"
@@ -433,19 +463,19 @@
"(Landroid/view/Surface;)I",
(void *)LegacyCameraDevice_nativeDetectSurfaceType },
{ "nativeDetectSurfaceDimens",
- "(Landroid/view/Surface;[I)V",
+ "(Landroid/view/Surface;[I)I",
(void *)LegacyCameraDevice_nativeDetectSurfaceDimens },
{ "nativeConfigureSurface",
- "(Landroid/view/Surface;III)V",
+ "(Landroid/view/Surface;III)I",
(void *)LegacyCameraDevice_nativeConfigureSurface },
{ "nativeProduceFrame",
- "(Landroid/view/Surface;[BIII)V",
+ "(Landroid/view/Surface;[BIII)I",
(void *)LegacyCameraDevice_nativeProduceFrame },
{ "nativeSetSurfaceFormat",
- "(Landroid/view/Surface;I)V",
+ "(Landroid/view/Surface;I)I",
(void *)LegacyCameraDevice_nativeSetSurfaceFormat },
{ "nativeSetSurfaceDimens",
- "(Landroid/view/Surface;II)V",
+ "(Landroid/view/Surface;II)I",
(void *)LegacyCameraDevice_nativeSetSurfaceDimens },
};
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 264a9ae..677c230 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -40,14 +40,23 @@
// ----------------------------------------------------------------------------
static const char* const kClassPathName = "android/media/AudioTrack";
+static const char* const kAudioAttributesClassPathName = "android/media/AudioAttributes";
-struct fields_t {
+struct audio_track_fields_t {
// these fields provide access from C++ to the...
jmethodID postNativeEventInJava; //... event post callback method
jfieldID nativeTrackInJavaObj; // stores in Java the native AudioTrack object
jfieldID jniData; // stores in Java additional resources used by the native AudioTrack
+ jfieldID fieldStreamType; // ... mStreamType field in the AudioTrack Java object
};
-static fields_t javaAudioTrackFields;
+struct audio_attributes_fields_t {
+ jfieldID fieldUsage; // AudioAttributes.mUsage
+ jfieldID fieldContentType; // AudioAttributes.mContentType
+ jfieldID fieldFlags; // AudioAttributes.mFlags
+ jfieldID fieldTags; // AudioAttributes.mTags
+};
+static audio_track_fields_t javaAudioTrackFields;
+static audio_attributes_fields_t javaAudioAttrFields;
struct audiotrack_callback_cookie {
jclass audioTrack_class;
@@ -66,12 +75,10 @@
sp<MemoryHeapBase> mMemHeap;
sp<MemoryBase> mMemBase;
audiotrack_callback_cookie mCallbackData;
- audio_stream_type_t mStreamType;
AudioTrackJniStorage() {
mCallbackData.audioTrack_class = 0;
mCallbackData.audioTrack_ref = 0;
- mStreamType = AUDIO_STREAM_DEFAULT;
}
~AudioTrackJniStorage() {
@@ -174,16 +181,21 @@
env->SetLongField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, (jlong)at.get());
return old;
}
-
// ----------------------------------------------------------------------------
static jint
android_media_AudioTrack_setup(JNIEnv *env, jobject thiz, jobject weak_this,
- jint streamType, jint sampleRateInHertz, jint javaChannelMask,
- jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession)
-{
+ jobject jaa,
+ jint sampleRateInHertz, jint javaChannelMask,
+ jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession) {
+
ALOGV("sampleRate=%d, audioFormat(from Java)=%d, channel mask=%x, buffSize=%d",
sampleRateInHertz, audioFormat, javaChannelMask, buffSizeInBytes);
+ if (jaa == 0) {
+ ALOGE("Error creating AudioTrack: invalid audio attributes");
+ return (jint) AUDIO_JAVA_ERROR;
+ }
+
// Java channel masks don't map directly to the native definition, but it's a simple shift
// to skip the two deprecated channel configurations "default" and "mono".
audio_channel_mask_t nativeChannelMask = ((uint32_t)javaChannelMask) >> 2;
@@ -195,9 +207,6 @@
uint32_t channelCount = popcount(nativeChannelMask);
- // stream type already checked in Java
- audio_stream_type_t atStreamType = (audio_stream_type_t) streamType;
-
// check the format.
// This function was called from Java, so we compare the format against the Java constants
audio_format_t format = audioFormatToNative(audioFormat);
@@ -251,10 +260,25 @@
// create the native AudioTrack object
sp<AudioTrack> lpTrack = new AudioTrack();
+ audio_attributes_t *paa = NULL;
+ // read the AudioAttributes values
+ paa = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t));
+ const jstring jtags = (jstring) env->GetObjectField(jaa, javaAudioAttrFields.fieldTags);
+ const char* tags = env->GetStringUTFChars(jtags, NULL);
+ // copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it
+ strncpy(paa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1);
+ env->ReleaseStringUTFChars(jtags, tags);
+ paa->usage = (audio_usage_t) env->GetIntField(jaa, javaAudioAttrFields.fieldUsage);
+ paa->content_type =
+ (audio_content_type_t) env->GetIntField(jaa, javaAudioAttrFields.fieldContentType);
+ paa->flags = env->GetIntField(jaa, javaAudioAttrFields.fieldFlags);
+
+ ALOGV("AudioTrack_setup for usage=%d content=%d flags=0x%#x tags=%s",
+ paa->usage, paa->content_type, paa->flags, paa->tags);
+
// initialize the callback information:
// this data will be passed with every AudioTrack callback
AudioTrackJniStorage* lpJniStorage = new AudioTrackJniStorage();
- lpJniStorage->mStreamType = atStreamType;
lpJniStorage->mCallbackData.audioTrack_class = (jclass)env->NewGlobalRef(clazz);
// we use a weak reference so the AudioTrack object can be garbage collected.
lpJniStorage->mCallbackData.audioTrack_ref = env->NewGlobalRef(weak_this);
@@ -266,17 +290,21 @@
case MODE_STREAM:
status = lpTrack->set(
- atStreamType,// stream type
- sampleRateInHertz,
- format,// word length, PCM
- nativeChannelMask,
- frameCount,
- audio_is_linear_pcm(format) ? AUDIO_OUTPUT_FLAG_NONE : AUDIO_OUTPUT_FLAG_DIRECT,
- audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)
- 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
- 0,// shared mem
- true,// thread can call Java
- sessionId);// audio session ID
+ AUDIO_STREAM_DEFAULT,// stream type
+ sampleRateInHertz,
+ format,// word length, PCM
+ nativeChannelMask,
+ frameCount,
+ AUDIO_OUTPUT_FLAG_NONE,
+ audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)
+ 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
+ 0,// shared mem
+ true,// thread can call Java
+ sessionId,// audio session ID
+ AudioTrack::TRANSFER_DEFAULT, // default transfer mode
+ NULL, // default offloadInfo
+ -1, -1, // default uid, pid values
+ paa);
break;
case MODE_STATIC:
@@ -288,17 +316,21 @@
}
status = lpTrack->set(
- atStreamType,// stream type
- sampleRateInHertz,
- format,// word length, PCM
- nativeChannelMask,
- frameCount,
- AUDIO_OUTPUT_FLAG_NONE,
- audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user));
- 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
- lpJniStorage->mMemBase,// shared mem
- true,// thread can call Java
- sessionId);// audio session ID
+ AUDIO_STREAM_DEFAULT,// stream type
+ sampleRateInHertz,
+ format,// word length, PCM
+ nativeChannelMask,
+ frameCount,
+ AUDIO_OUTPUT_FLAG_NONE,
+ audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user));
+ 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack
+ lpJniStorage->mMemBase,// shared mem
+ true,// thread can call Java
+ sessionId,// audio session ID
+ AudioTrack::TRANSFER_DEFAULT, // default transfer mode
+ NULL, // default offloadInfo
+ -1, -1, // default uid, pid values
+ paa);
break;
default:
@@ -333,10 +365,21 @@
//ALOGV("storing lpJniStorage: %x\n", (long)lpJniStorage);
env->SetLongField(thiz, javaAudioTrackFields.jniData, (jlong)lpJniStorage);
+ // since we had audio attributes, the stream type was derived from them during the
+ // creation of the native AudioTrack: push the same value to the Java object
+ env->SetIntField(thiz, javaAudioTrackFields.fieldStreamType, (jint) lpTrack->streamType());
+ // audio attributes were copied in AudioTrack creation
+ free(paa);
+ paa = NULL;
+
+
return (jint) AUDIO_JAVA_SUCCESS;
// failures:
native_init_failure:
+ if (paa != NULL) {
+ free(paa);
+ }
if (nSession != NULL) {
env->ReleasePrimitiveArrayCritical(jSession, nSession, 0);
}
@@ -948,7 +991,7 @@
{"native_stop", "()V", (void *)android_media_AudioTrack_stop},
{"native_pause", "()V", (void *)android_media_AudioTrack_pause},
{"native_flush", "()V", (void *)android_media_AudioTrack_flush},
- {"native_setup", "(Ljava/lang/Object;IIIIII[I)I",
+ {"native_setup", "(Ljava/lang/Object;Ljava/lang/Object;IIIII[I)I",
(void *)android_media_AudioTrack_setup},
{"native_finalize", "()V", (void *)android_media_AudioTrack_finalize},
{"native_release", "()V", (void *)android_media_AudioTrack_release},
@@ -992,6 +1035,7 @@
#define JAVA_POSTEVENT_CALLBACK_NAME "postEventFromNative"
#define JAVA_NATIVETRACKINJAVAOBJ_FIELD_NAME "mNativeTrackInJavaObj"
#define JAVA_JNIDATA_FIELD_NAME "mJniData"
+#define JAVA_STREAMTYPE_FIELD_NAME "mStreamType"
// ----------------------------------------------------------------------------
// preconditions:
@@ -1041,7 +1085,7 @@
ALOGE("Can't find AudioTrack.%s", JAVA_NATIVETRACKINJAVAOBJ_FIELD_NAME);
return -1;
}
- // jniData;
+ // jniData
javaAudioTrackFields.jniData = env->GetFieldID(
audioTrackClass,
JAVA_JNIDATA_FIELD_NAME, "J");
@@ -1049,6 +1093,33 @@
ALOGE("Can't find AudioTrack.%s", JAVA_JNIDATA_FIELD_NAME);
return -1;
}
+ // fieldStreamType
+ javaAudioTrackFields.fieldStreamType = env->GetFieldID(audioTrackClass,
+ JAVA_STREAMTYPE_FIELD_NAME, "I");
+ if (javaAudioTrackFields.fieldStreamType == NULL) {
+ ALOGE("Can't find AudioTrack.%s", JAVA_STREAMTYPE_FIELD_NAME);
+ return -1;
+ }
+
+ // Get the AudioAttributes class and fields
+ jclass audioAttrClass = env->FindClass(kAudioAttributesClassPathName);
+ if (audioAttrClass == NULL) {
+ ALOGE("Can't find %s", kAudioAttributesClassPathName);
+ return -1;
+ }
+ jclass audioAttributesClassRef = (jclass)env->NewGlobalRef(audioAttrClass);
+ javaAudioAttrFields.fieldUsage = env->GetFieldID(audioAttributesClassRef, "mUsage", "I");
+ javaAudioAttrFields.fieldContentType
+ = env->GetFieldID(audioAttributesClassRef, "mContentType", "I");
+ javaAudioAttrFields.fieldFlags = env->GetFieldID(audioAttributesClassRef, "mFlags", "I");
+ javaAudioAttrFields.fieldTags = env->GetFieldID(audioAttributesClassRef, "mFormattedTags",
+ "Ljava/lang/String;");
+ env->DeleteGlobalRef(audioAttributesClassRef);
+ if (javaAudioAttrFields.fieldUsage == NULL || javaAudioAttrFields.fieldContentType == NULL
+ || javaAudioAttrFields.fieldFlags == NULL || javaAudioAttrFields.fieldTags == NULL) {
+ ALOGE("Can't initialize AudioAttributes fields");
+ return -1;
+ }
return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
}
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index bc5e1b3..6f89800 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -214,7 +214,8 @@
return android_net_utils_runDhcpCommon(env, clazz, ifname, info, false);
}
-static jboolean android_net_utils_runDhcpRenew(JNIEnv* env, jobject clazz, jstring ifname, jobject info)
+static jboolean android_net_utils_runDhcpRenew(JNIEnv* env, jobject clazz, jstring ifname,
+ jobject info)
{
return android_net_utils_runDhcpCommon(env, clazz, ifname, info, true);
}
@@ -252,14 +253,14 @@
}
}
-static void android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
+static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
{
- setNetworkForProcess(netId);
+ return (jboolean) !setNetworkForProcess(netId);
}
-static void android_net_utils_unbindProcessToNetwork(JNIEnv *env, jobject thiz)
+static jboolean android_net_utils_unbindProcessToNetwork(JNIEnv *env, jobject thiz)
{
- setNetworkForProcess(NETID_UNSET);
+ return (jboolean) !setNetworkForProcess(NETID_UNSET);
}
static jint android_net_utils_getNetworkBoundToProcess(JNIEnv *env, jobject thiz)
@@ -267,19 +268,21 @@
return getNetworkForProcess();
}
-static void android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz, jint netId)
+static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz,
+ jint netId)
{
- setNetworkForResolv(netId);
+ return (jboolean) !setNetworkForResolv(netId);
}
-static void android_net_utils_unbindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz)
+static jboolean android_net_utils_unbindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz)
{
- setNetworkForResolv(NETID_UNSET);
+ return (jboolean) !setNetworkForResolv(NETID_UNSET);
}
-static void android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jint socket, jint netId)
+static jboolean android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jint socket,
+ jint netId)
{
- setNetworkForSocket(netId, socket);
+ return (jboolean) !setNetworkForSocket(netId, socket);
}
// ----------------------------------------------------------------------------
@@ -299,12 +302,12 @@
{ "releaseDhcpLease", "(Ljava/lang/String;)Z", (void *)android_net_utils_releaseDhcpLease },
{ "getDhcpError", "()Ljava/lang/String;", (void*) android_net_utils_getDhcpError },
{ "markSocket", "(II)V", (void*) android_net_utils_markSocket },
- { "bindProcessToNetwork", "(I)V", (void*) android_net_utils_bindProcessToNetwork },
+ { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
{ "getNetworkBoundToProcess", "()I", (void*) android_net_utils_getNetworkBoundToProcess },
- { "unbindProcessToNetwork", "()V", (void*) android_net_utils_unbindProcessToNetwork },
- { "bindProcessToNetworkForHostResolution", "(I)V", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
- { "unbindProcessToNetworkForHostResolution", "()V", (void*) android_net_utils_unbindProcessToNetworkForHostResolution },
- { "bindSocketToNetwork", "(II)V", (void*) android_net_utils_bindSocketToNetwork },
+ { "unbindProcessToNetwork", "()Z", (void*) android_net_utils_unbindProcessToNetwork },
+ { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
+ { "unbindProcessToNetworkForHostResolution", "()Z", (void*) android_net_utils_unbindProcessToNetworkForHostResolution },
+ { "bindSocketToNetwork", "(II)Z", (void*) android_net_utils_bindSocketToNetwork },
};
int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 01f4d3a..a6b65cc 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -45,6 +45,8 @@
#define POLICY_DEBUG 0
#define GUARD_THREAD_PRIORITY 0
+#define DEBUG_PROC(x) //x
+
using namespace android;
#if GUARD_THREAD_PRIORITY
@@ -725,6 +727,7 @@
const char term = (char)(mode&PROC_TERM_MASK);
const jsize start = i;
if (i >= endIndex) {
+ DEBUG_PROC(ALOGW("Ran off end of data @%d", i));
res = JNI_FALSE;
break;
}
@@ -822,19 +825,20 @@
return JNI_FALSE;
}
int fd = open(file8, O_RDONLY);
- env->ReleaseStringUTFChars(file, file8);
if (fd < 0) {
- //ALOGW("Unable to open process file: %s\n", file8);
+ DEBUG_PROC(ALOGW("Unable to open process file: %s\n", file8));
+ env->ReleaseStringUTFChars(file, file8);
return JNI_FALSE;
}
+ env->ReleaseStringUTFChars(file, file8);
char buffer[256];
const int len = read(fd, buffer, sizeof(buffer)-1);
close(fd);
if (len < 0) {
- //ALOGW("Unable to open process file: %s fd=%d\n", file8, fd);
+ DEBUG_PROC(ALOGW("Unable to open process file: %s fd=%d\n", file8, fd));
return JNI_FALSE;
}
buffer[len] = 0;
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index ca8fb30..b74bf0f 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -32,6 +32,7 @@
#include <SkCanvas.h>
#include <SkMatrix.h>
#include <SkPaint.h>
+#include <SkPorterDuff.h>
#include <SkRegion.h>
#include <SkScalerContext.h>
#include <SkTemplates.h>
@@ -42,14 +43,9 @@
#include <RenderNode.h>
#include <CanvasProperty.h>
-#ifdef USE_MINIKIN
#include <minikin/Layout.h>
#include "MinikinSkia.h"
#include "MinikinUtils.h"
-#endif
-
-#include <TextLayout.h>
-#include <TextLayoutCache.h>
namespace android {
@@ -437,9 +433,10 @@
}
static void android_view_GLES20Canvas_drawColor(JNIEnv* env, jobject clazz,
- jlong rendererPtr, jint color, jint mode) {
+ jlong rendererPtr, jint color, jint modeHandle) {
DisplayListRenderer* renderer = reinterpret_cast<DisplayListRenderer*>(rendererPtr);
- renderer->drawColor(color, static_cast<SkXfermode::Mode>(mode));
+ SkPorterDuff::Mode mode = static_cast<SkPorterDuff::Mode>(modeHandle);
+ renderer->drawColor(color, SkPorterDuff::ToXfermodeMode(mode));
}
static void android_view_GLES20Canvas_drawRect(JNIEnv* env, jobject clazz,
@@ -567,23 +564,6 @@
// Text
// ----------------------------------------------------------------------------
-// TODO: this is moving to MinikinUtils, remove with USE_MINIKIN ifdef
-static float xOffsetForTextAlign(SkPaint* paint, float totalAdvance) {
- switch (paint->getTextAlign()) {
- case SkPaint::kCenter_Align:
- return -totalAdvance / 2.0f;
- break;
- case SkPaint::kRight_Align:
- return -totalAdvance;
- break;
- default:
- break;
- }
- return 0;
-}
-
-#ifdef USE_MINIKIN
-
class RenderTextFunctor {
public:
RenderTextFunctor(const Layout& layout, DisplayListRenderer* renderer, jfloat x, jfloat y,
@@ -630,38 +610,16 @@
delete[] glyphs;
delete[] pos;
}
-#endif
static void renderText(DisplayListRenderer* renderer, const jchar* text, int count,
jfloat x, jfloat y, int bidiFlags, SkPaint* paint, TypefaceImpl* typeface) {
-#ifdef USE_MINIKIN
Layout layout;
std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
layout.doLayout(text, 0, count, count, css);
- x += xOffsetForTextAlign(paint, layout.getAdvance());
+ x += MinikinUtils::xOffsetForTextAlign(paint, layout);
renderTextLayout(renderer, &layout, x, y, paint);
-#else
- sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
- text, 0, count, count, bidiFlags);
- if (value == NULL) {
- return;
- }
- const jchar* glyphs = value->getGlyphs();
- size_t glyphsCount = value->getGlyphsCount();
- jfloat totalAdvance = value->getTotalAdvance();
- x += xOffsetForTextAlign(paint, totalAdvance);
- const float* positions = value->getPos();
- int bytesCount = glyphsCount * sizeof(jchar);
- const SkRect& r = value->getBounds();
- android::uirenderer::Rect bounds(r.fLeft, r.fTop, r.fRight, r.fBottom);
- bounds.translate(x, y);
-
- renderer->drawText((const char*) glyphs, bytesCount, glyphsCount,
- x, y, positions, paint, totalAdvance, bounds);
-#endif
}
-#ifdef USE_MINIKIN
class RenderTextOnPathFunctor {
public:
RenderTextOnPathFunctor(const Layout& layout, DisplayListRenderer* renderer, float hOffset,
@@ -686,12 +644,10 @@
SkPaint* paint;
SkPath* path;
};
-#endif
static void renderTextOnPath(DisplayListRenderer* renderer, const jchar* text, int count,
SkPath* path, jfloat hOffset, jfloat vOffset, int bidiFlags, SkPaint* paint,
TypefaceImpl* typeface) {
-#ifdef USE_MINIKIN
Layout layout;
std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
layout.doLayout(text, 0, count, count, css);
@@ -702,48 +658,16 @@
RenderTextOnPathFunctor f(layout, renderer, hOffset, vOffset, paint, path);
MinikinUtils::forFontRun(layout, paint, f);
paint->setTextAlign(align);
-#else
- sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
- text, 0, count, count, bidiFlags);
- if (value == NULL) {
- return;
- }
- const jchar* glyphs = value->getGlyphs();
- size_t glyphsCount = value->getGlyphsCount();
- int bytesCount = glyphsCount * sizeof(jchar);
- renderer->drawTextOnPath((const char*) glyphs, bytesCount, glyphsCount, path,
- hOffset, vOffset, paint);
-#endif
}
static void renderTextRun(DisplayListRenderer* renderer, const jchar* text,
jint start, jint count, jint contextCount, jfloat x, jfloat y,
int bidiFlags, SkPaint* paint, TypefaceImpl* typeface) {
-#ifdef USE_MINIKIN
Layout layout;
std::string css = MinikinUtils::setLayoutProperties(&layout, paint, bidiFlags, typeface);
layout.doLayout(text, start, count, contextCount, css);
- x += xOffsetForTextAlign(paint, layout.getAdvance());
+ x += MinikinUtils::xOffsetForTextAlign(paint, layout);
renderTextLayout(renderer, &layout, x, y, paint);
-#else
- sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
- text, start, count, contextCount, bidiFlags);
- if (value == NULL) {
- return;
- }
- const jchar* glyphs = value->getGlyphs();
- size_t glyphsCount = value->getGlyphsCount();
- jfloat totalAdvance = value->getTotalAdvance();
- x += xOffsetForTextAlign(paint, totalAdvance);
- const float* positions = value->getPos();
- int bytesCount = glyphsCount * sizeof(jchar);
- const SkRect& r = value->getBounds();
- android::uirenderer::Rect bounds(r.fLeft, r.fTop, r.fRight, r.fBottom);
- bounds.translate(x, y);
-
- renderer->drawText((const char*) glyphs, bytesCount, glyphsCount,
- x, y, positions, paint, totalAdvance, bounds);
-#endif
}
static void android_view_GLES20Canvas_drawTextArray(JNIEnv* env, jobject clazz,
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 3ffde2d..6ba22bf 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -456,16 +456,9 @@
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
RenderPropertyAnimator* animator = reinterpret_cast<RenderPropertyAnimator*>(animatorPtr);
renderNode->addAnimator(animator);
+ animator->start();
}
-static void android_view_RenderNode_removeAnimator(JNIEnv* env, jobject clazz,
- jlong renderNodePtr, jlong animatorPtr) {
- RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
- RenderPropertyAnimator* animator = reinterpret_cast<RenderPropertyAnimator*>(animatorPtr);
- renderNode->removeAnimator(animator);
-}
-
-
#endif // USE_OPENGL_RENDERER
// ----------------------------------------------------------------------------
@@ -546,7 +539,6 @@
{ "nGetPivotY", "(J)F", (void*) android_view_RenderNode_getPivotY },
{ "nAddAnimator", "(JJ)V", (void*) android_view_RenderNode_addAnimator },
- { "nRemoveAnimator", "(JJ)V", (void*) android_view_RenderNode_removeAnimator },
#endif
};
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp
index d689864..de3dd16 100644
--- a/core/jni/android_view_RenderNodeAnimator.cpp
+++ b/core/jni/android_view_RenderNodeAnimator.cpp
@@ -149,6 +149,11 @@
animator->setInterpolator(interpolator);
}
+static void cancel(JNIEnv* env, jobject clazz, jlong animatorPtr) {
+ BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
+ animator->cancel();
+}
+
#endif
// ----------------------------------------------------------------------------
@@ -168,6 +173,7 @@
{ "nSetStartDelay", "(JJ)V", (void*) setStartDelay },
{ "nGetStartDelay", "(J)J", (void*) getStartDelay },
{ "nSetInterpolator", "(JJ)V", (void*) setInterpolator },
+ { "nCancel", "(J)V", (void*) cancel },
#endif
};
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index fa1a563..ca897d5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -156,16 +156,12 @@
android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED" />
<protected-broadcast android:name="android.btopp.intent.action.INCOMING_FILE_NOTIFICATION" />
- <protected-broadcast android:name="android.btopp.intent.action.BT_OPP_HANDOVER_STARTED" />
- <protected-broadcast android:name="android.btopp.intent.action.TRANSFER_COMPLETE" />
<protected-broadcast android:name="android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT" />
- <protected-broadcast android:name="android.btopp.intent.action.BT_OPP_TRANSFER_PROGRESS" />
<protected-broadcast android:name="android.btopp.intent.action.LIST" />
<protected-broadcast android:name="android.btopp.intent.action.OPEN_OUTBOUND" />
<protected-broadcast android:name="android.btopp.intent.action.HIDE_COMPLETE" />
<protected-broadcast android:name="android.btopp.intent.action.CONFIRM" />
<protected-broadcast android:name="android.btopp.intent.action.HIDE" />
- <protected-broadcast android:name="android.btopp.intent.action.BT_OPP_TRANSFER_DONE" />
<protected-broadcast android:name="android.btopp.intent.action.RETRY" />
<protected-broadcast android:name="android.btopp.intent.action.OPEN" />
<protected-broadcast android:name="android.btopp.intent.action.OPEN_INBOUND" />
@@ -284,7 +280,13 @@
<protected-broadcast
android:name="android.intent.action.PERMISSION_RESPONSE_RECEIVED" />
<!-- Defined in RestrictionsManager -->
+
<protected-broadcast android:name="android.intent.action.REQUEST_PERMISSION" />
+ <protected-broadcast android:name="android.nfc.handover.intent.action.HANDOVER_STARTED" />
+ <protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_DONE" />
+ <protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_PROGRESS" />
+ <protected-broadcast android:name="android.nfc.handover.intent.action.TRANSFER_DONE" />
+
<!-- ====================================== -->
<!-- Permissions for things that cost money -->
@@ -650,13 +652,20 @@
android:permissionGroupFlags="personalInfo"
android:priority="280" />
- <!-- Allows an application to add voicemails into the system. -->
+ <!-- Allows an application to add voicemails into the system. -->
<permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"
android:permissionGroup="android.permission-group.VOICEMAIL"
android:protectionLevel="dangerous"
android:label="@string/permlab_addVoicemail"
android:description="@string/permdesc_addVoicemail" />
+ <!-- Allows an application to remove any voicemails from the system. -->
+ <permission android:name="com.android.voicemail.permission.REMOVE_VOICEMAIL"
+ android:permissionGroup="android.permission-group.VOICEMAIL"
+ android:protectionLevel="dangerous"
+ android:label="@string/permlab_removeVoicemail"
+ android:description="@string/permdesc_removeVoicemail" />
+
<!-- Allows an application to read all the voicemails in the system. -->
<permission android:name="com.android.voicemail.permission.READ_ALL_VOICEMAIL"
android:permissionGroup="android.permission-group.VOICEMAIL"
@@ -870,6 +879,14 @@
android:permissionGroup="android.permission-group.NETWORK"
android:protectionLevel="signature|system" />
+ <!-- Allows sending and receiving handover transfer status from Wifi and Bluetooth
+ @hide
+ -->
+ <permission android:name="android.permission.NFC_HANDOVER_STATUS"
+ android:label="@string/permlab_handoverStatus"
+ android:description="@string/permdesc_handoverStatus"
+ android:protectionLevel="signature|system" />
+
<!-- ================================== -->
<!-- Permissions for accessing accounts -->
<!-- ================================== -->
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index cb93530..60fa084 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -189,7 +189,7 @@
<string name="safeMode" msgid="2788228061547930246">"Veiligmodus"</string>
<string name="android_system_label" msgid="6577375335728551336">"Android-stelsel"</string>
<string name="user_owner_label" msgid="6465364741001216388">"Persoonlike programme"</string>
- <string name="managed_profile_label" msgid="4287077106125758391">"Android Werk"</string>
+ <string name="managed_profile_label" msgid="4287077106125758391">"Android-werk"</string>
<string name="permgrouplab_costMoney" msgid="5429808217861460401">"Dienste wat jou geld kos"</string>
<string name="permgroupdesc_costMoney" msgid="3293301903409869495">"Doen dinge wat jou geld kan kos."</string>
<string name="permgrouplab_messages" msgid="7521249148445456662">"Jou boodskappe"</string>
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Kies jaar"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> gekies"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> uitgevee"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Werk-<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Gebruik Sluit-na-program?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Sluit-na-program sluit die skerm in \'n enkele program.\n\nOm uit te gaan, druk en hou die knoppie vir onlangse programme $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NEE"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"BEGIN"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Begin Sluit-na-program"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Gaan uit Sluit-na-program"</string>
</resources>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 66faf61..7a08236 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1725,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"ዓመት ይምረጡ"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> ተመርጧል"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> ተሰርዟል"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"ስራ <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 7d1117b..d53daba 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"تحديد العام"</string>
<string name="item_is_selected" msgid="949687401682476608">"تم تحديد <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"تم حذف <xliff:g id="KEY">%1$s</xliff:g>"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> المخصص للعمل"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"هل تريد استخدام قفل تشغيل الجهاز على تطبيق؟"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"تتيح ميزة \"قفل تشغيل الجهاز على تطبيق\" تأمين الشاشة وقصر استخدامها على تطبيق واحد.\n\nللخروج اضغط مع الاستمرار على زر التطبيقات الحديثة $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"لا"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"بدء"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"بدء قفل تشغيل الجهاز على تطبيق"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"خروج من قفل تشغيل الجهاز على تطبيق"</string>
</resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index b9c4f46..d77971c 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"Разрешава на приложението да навие инсталирано приложение будилник. Някои будилници може да не изпълнят тази функция."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"добавяне на гласова поща"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Разрешава на приложението да добавя съобщения към входящата ви гласова поща."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"четене на цялата гласова поща"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Разрешава на приложението да чете цялата ви гласова поща."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"промяна на разрешенията за местоположение в браузъра"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Разрешава на приложението да променя разрешенията на браузъра за местоположение. Злонамерените приложения могат да използват това, за да изпращат информация за местоположението до произволни уебсайтове."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"проверка на пакетите"</string>
@@ -1727,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"Избиране на година"</string>
<string name="item_is_selected" msgid="949687401682476608">"Избрахте <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"Изтрихте <xliff:g id="KEY">%1$s</xliff:g>"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> за работа"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 18389b6a..46eeae8 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Selecciona un any"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> seleccionat"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> suprimit"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de la feina"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Vols fer servir la funció Bloqueja una aplicació?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"La funció Bloqueja una aplicació bloqueja la visualització a una sola aplicació.\n\nPer sortir, mantén premut el botó d\'aplicacions recents $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"INICI"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Inicia la funció Bloqueja una aplicació"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Surt de la funció Bloqueja una aplicació"</string>
</resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index aeb19d0..eb28a23 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Vyberte rok"</string>
<string name="item_is_selected" msgid="949687401682476608">"Vybrána položka <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"Číslice <xliff:g id="KEY">%1$s</xliff:g> byla smazána"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Pracovní <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Použít Uzamčení v aplikaci?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Funkce Uzamčení v aplikaci uzamkne obrazovku na jedinou aplikaci.\n\nChcete-li tento režim opustit, stiskněte a podržte tlačítko posledních aplikací $."</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NE"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"SPUSTIT"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Spustit Uzamčení v aplikaci"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Opustit Uzamčení v aplikaci"</string>
</resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 2996f75..9ddbf87 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1280,7 +1280,7 @@
<string name="time_picker_dialog_title" msgid="8349362623068819295">"Angiv tidspunkt"</string>
<string name="date_picker_dialog_title" msgid="5879450659453782278">"Angiv dato"</string>
<string name="date_time_set" msgid="5777075614321087758">"Angiv"</string>
- <string name="date_time_done" msgid="2507683751759308828">"Udført"</string>
+ <string name="date_time_done" msgid="2507683751759308828">"Udfør"</string>
<string name="perms_new_perm_prefix" msgid="8257740710754301407"><font size="12" fgcolor="#ff33b5e5">"NY: "</font></string>
<string name="perms_description_app" msgid="5139836143293299417">"Leveret af <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
<string name="no_permissions" msgid="7283357728219338112">"Der kræves ingen tilladelser"</string>
@@ -1424,7 +1424,7 @@
<item quantity="one" msgid="8167147081136579439">"1 match"</item>
<item quantity="other" msgid="4641872797067609177">"<xliff:g id="INDEX">%d</xliff:g> af <xliff:g id="TOTAL">%d</xliff:g>"</item>
</plurals>
- <string name="action_mode_done" msgid="7217581640461922289">"Udført"</string>
+ <string name="action_mode_done" msgid="7217581640461922289">"Udfør"</string>
<string name="progress_unmounting" product="nosdcard" msgid="3923810448507612746">"Demonterer USB-lageret..."</string>
<string name="progress_unmounting" product="default" msgid="1327894998409537190">"Demonterer SD-kortet..."</string>
<string name="progress_erasing" product="nosdcard" msgid="4521573321524340058">"Sletter USB-lageret..."</string>
@@ -1475,7 +1475,7 @@
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Annuller"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Slet"</string>
- <string name="keyboardview_keycode_done" msgid="1992571118466679775">"Udført"</string>
+ <string name="keyboardview_keycode_done" msgid="1992571118466679775">"Udfør"</string>
<string name="keyboardview_keycode_mode_change" msgid="4547387741906537519">"Ændring af tilstand"</string>
<string name="keyboardview_keycode_shift" msgid="2270748814315147690">"Shift"</string>
<string name="keyboardview_keycode_enter" msgid="2985864015076059467">"Angiv"</string>
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Vælg år"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> er valgt"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> er slettet"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> – arbejde"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Vil du bruge Lås-til-app?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Med Lås-til-app låses skærmen, så den kun viser én app.\n\nHvis du vil afslutte denne tilstand, skal du trykke på knappen for seneste apps og holde fingeren nede $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NEJ"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Start Lås-til-app"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Afslut Lås-til-app"</string>
</resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index fe5c139..6f08ba2 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Jahr auswählen"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> ausgewählt"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> gelöscht"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (geschäftlich)"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"\"Auf App einschränken\" verwenden?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Mit \"Auf App einschränken\" wird das Display in einer einzelnen App gesperrt.\n\nZum Beenden der Sperre berühren und halten Sie die Schaltfläche für die letzten Apps $."</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"Nein"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"Starten"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"\"Auf App einschränken\" starten"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"\"Auf App einschränken\" beenden"</string>
</resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 1a70d19..0539bc9 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Επιλογή έτους"</string>
<string name="item_is_selected" msgid="949687401682476608">"Επιλέχτηκε το στοιχείο <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> διαγράφηκε"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Εργασία <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Χρήση λειτουργίας lock-to-app;"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Η λειτουργία Lock-to-app κλειδώνει την οθόνη σε μία μόνο εφαρμογή.\n\nΓια έξοδο πατήστε παρατεταμένα το κουμπί των πρόσφατων εφαρμογών $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"ΟΧΙ"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"ΕΝΑΡΞΗ"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Έναρξη Lock-to-app"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Έξοδος από Lock-to-app"</string>
</resources>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index c659999..18e11df 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Select year"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selected"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> deleted"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Work <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Use lock-to-app?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app locks the display in a single app.\n\nTo exit press and hold the recent apps button $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Start Lock-to-app"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Exit Lock-to-app"</string>
</resources>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index c659999..18e11df 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Select year"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selected"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> deleted"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Work <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Use lock-to-app?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app locks the display in a single app.\n\nTo exit press and hold the recent apps button $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Start Lock-to-app"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Exit Lock-to-app"</string>
</resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index b1108bf2..2451d62 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"Permite que la aplicación establezca una alarma en una aplicación de alarma instalada. Es posible que algunas aplicaciones de alarma no incluyan esta función."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"agregar correo de voz"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que la aplicación agregue mensajes a la bandeja de entrada de tu buzón de voz."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"Consultar todos los mensajes del buzón de voz"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permite que la aplicación consulte todos los mensajes del buzón de voz."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modificar los permisos de ubicación geográfica del navegador"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que la aplicación modifique los permisos de ubicación geográfica del navegador. Las aplicaciones maliciosas pueden utilizar esto para permitir el envío de información de ubicación a sitios web arbitrarios."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"Verificar paquetes"</string>
@@ -1727,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"Seleccionar año"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> seleccionado"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> borrado"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de trabajo"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index f47f5c0..0bc3a92 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Seleccionar año"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> seleccionado"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> eliminado"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de trabajo"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"¿Usar bloqueo de aplicación?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"El bloqueo de aplicación bloquea la pantalla en una sola aplicación.\n\nPara salir, mantén pulsado el botón de aplicaciones recientes $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"INICIAR"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Iniciar bloqueo de aplicación"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Salir de bloqueo de aplicación"</string>
</resources>
diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml
index 0df3aca..4012f09 100644
--- a/core/res/res/values-et-rEE/strings.xml
+++ b/core/res/res/values-et-rEE/strings.xml
@@ -1725,4 +1725,12 @@
<string name="select_year" msgid="7952052866994196170">"Aasta valimine"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> on valitud"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> on kustutatud"</string>
+ <!-- no translation found for managed_profile_label_badge (2355652472854327647) -->
+ <skip />
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Kas soovite kasutada rakendusele lukustamist?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Rakendusele lukustamise funktsioon lukustab kuva ühele rakendusele.\n\nVäljumiseks hoidke hiljutiste rakenduste nuppu $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"EI"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"KÄIVITA"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Käivita rakendusele lukustamine"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Välju rakendusele lukustamisest"</string>
</resources>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index d652544..30136e76 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"انتخاب سال"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> انتخاب شد"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> حذف شد"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> محل کار"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"از «قفل برنامه» استفاده شود؟"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"«قفل برنامه» نمایشگر را در یک برنامه قفل میکند.\n\nبرای خروج، دکمه برنامههای جدید $ را فشار داده و نگهدارید"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"خیر"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"شروع"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"راهاندازی «قفل برنامه»"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"خروج از «قفل برنامه»"</string>
</resources>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 802fb24..f833733 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"Antaa sovelluksen asettaa hälytyksen sisäiseen herätyskellosovellukseen. Jotkin herätyskellosovellukset eivät välttämättä käytä tätä ominaisuutta."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"lisää vastaajaviesti"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Antaa sovelluksen lisätä viestejä saapuneisiin vastaajaviesteihin."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"kaikkien vastaajaviestien luku"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Antaa sovelluksen lukea kaikki vastaajaviestisi."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"selaimen maantieteellisen sijainnin lupien muokkaaminen"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Antaa sovelluksen muokata Selaimen maantieteellisen sijainnin lupia. Haitalliset sovellukset voivat sallia tällä sijaintitietojen lähettämisen mielivaltaisiin sivustoihin."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"vahvista paketteja"</string>
@@ -1727,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"Valitse vuosi"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> on valittu"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> poistettiin"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (työ)"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index ec4c2d6..5ffa2e7 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"Permet à l\'application de régler la sonnerie d\'une fonction de réveil installée sur votre appareil. Cette fonctionnalité n\'est pas compatible avec toutes les applications de réveils."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"ajouter des messages vocaux"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permet à l\'application d\'ajouter des messages à votre messagerie vocale."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"accéder à tous les messages vocaux"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permet à l\'application d\'accéder à tous vos messages vocaux."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modifier les autorisations de géolocalisation du navigateur"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permet à l\'application de modifier les autorisations de géolocalisation du navigateur. Des applications malveillantes peuvent exploiter cette fonctionnalité pour permettre l\'envoi de données de localisation à des sites Web arbitraires."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"vérifier les paquets"</string>
@@ -1727,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"Sélectionnez une année"</string>
<string name="item_is_selected" msgid="949687401682476608">"« <xliff:g id="ITEM">%1$s</xliff:g> » a été sélectionné"</string>
<string name="deleted_key" msgid="7659477886625566590">"« <xliff:g id="KEY">%1$s</xliff:g> » a été supprimé"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (travail)"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 57ee762..e2c7196 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1725,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"Sélectionner une année"</string>
<string name="item_is_selected" msgid="949687401682476608">"\"<xliff:g id="ITEM">%1$s</xliff:g>\" sélectionné"</string>
<string name="deleted_key" msgid="7659477886625566590">"\"<xliff:g id="KEY">%1$s</xliff:g>\" supprimé"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (travail)"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 8c2c025..8cbb095 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"ऐप्स को इंस्टॉल किए गए अलार्म घड़ी ऐप्स में अलार्म सेट करने देता है. हो सकता है कुछ अलार्म घड़ी ऐप्स में यह सुविधा न हो."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"ध्वनिमेल जोड़ें"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"ऐप्स को आपके ध्वनिमेल इनबॉक्स में संदेश जोड़ने देता है."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"सभी ध्वनिमेल पढ़ें"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"ऐप्स को आपके सभी ध्वनिमेल पढ़ने देती है."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ब्राउज़र भौगोलिक-स्थान अनुमतियों को बदलें"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ऐप्स को ब्राउज़र के भौगोलिक-स्थान की अनुमतियां संशोधित करने देता है. दुर्भावनापूर्ण ऐप्स इसका उपयोग एकपक्षीय वेबसाइट को स्थान जानकारी भेजने में कर सकते हैं."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"पैकेज सत्यापित करें"</string>
@@ -1727,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"वर्ष चुनें"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> चयनित"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> को हटा दिया गया"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"कार्यस्थल का <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"ऐप्स-पर-लॉक करें का उपयोग करें?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"ऐप्स-पर-लॉक करें, डिस्प्ले को किसी एकल ऐप्स में लॉक कर देता है.\n\nबाहर निकलने के लिए हाल ही के ऐप्स बटन $ को दबाए रखें"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"नहीं"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"प्रारंभ करें"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"ऐप्स-पर-लॉक करें प्रारंभ करें"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"ऐप्स-पर-लॉक करें से बाहर निकलें"</string>
</resources>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 65e64ec..a427eb3 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1364,8 +1364,8 @@
<string name="permdesc_trust_listener" msgid="8233895334214716864">"Omogućuje aplikaciji praćenje promjena pouzdanog stanja."</string>
<string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Pružanje agenta za pouzdanost."</string>
<string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Omogućuje aplikaciji pružanje agenta za pouzdanost."</string>
- <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Pokretanje izbornika postavki pouzdanog agenta."</string>
- <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Aplikaciji omogućuje pokretanje aktivnosti koja mijenja ponašanje pouzdanog agenta."</string>
+ <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Pokretanje izbornika postavki agenta za pouzdanost."</string>
+ <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Aplikaciji omogućuje pokretanje aktivnosti koja mijenja ponašanje agenta za pouzdanost."</string>
<string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Povezivanje s uslugom pouzdanog predstavnika"</string>
<string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Omogućuje aplikaciji povezivanje s uslugom pouzdanog predstavnika."</string>
<string name="permlab_recovery" msgid="3157024487744125846">"Interakcija s ažuriranjem i sustavom za oporavak"</string>
@@ -1725,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"Odaberite godinu"</string>
<string name="item_is_selected" msgid="949687401682476608">"Odabrana je stavka <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"Izbrisan je broj <xliff:g id="KEY">%1$s</xliff:g>"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> za posao"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 498e28a..1fbfdef 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Válassza ki az évet"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> kiválasztva"</string>
<string name="deleted_key" msgid="7659477886625566590">"A(z) <xliff:g id="KEY">%1$s</xliff:g> érték törölve"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Munkahelyi <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Használni szeretné az alkalmazászárolást?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Az alkalmazászárolás lezárja a kijelzőt az adott alkalmazásban.\n\nA kilépéshez tartsa lenyomva a legutóbbi alkalmazások gombot ($)."</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NEM"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"INDÍT"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Az alkalmazászárolás indítása"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Kilépés az alkalmazászárolásból"</string>
</resources>
diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml
index 97e9ffc1..27a65a3 100644
--- a/core/res/res/values-hy-rAM/strings.xml
+++ b/core/res/res/values-hy-rAM/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"Թույլ է տալիս հավելվածին սահմանել զարթուցիչի ծրագրում տեղադրված ազդանշանը: Զարթուցիչի որոշ հավելվածներ չեն կարող կիրառել այս հատկությունը:"</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"ավելացնել ձայնային փոստ"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Թույլ է տալիս հավելվածին ավելացնել հաղորդագրություններ ձեր ձայնային փոստի արկղում:"</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"կարդալ ձայնային հաղորդագրությունները"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Թույլ է տալիս հավելվածին կարդալ ձեր բոլոր ձայնային հաղորդագրությունները:"</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"փոփոխել դիտարկչի աշխարհագրական տեղանքի թույլտվությունները"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Թույլ է տալիս հավելվածին փոփոխել զննարկչի աշխարհագրական դիրքի թույլտվությունները: Վնասարար հավելվածները կարող են օգտագործել սա` թույլատրելու ուղարկել տեղադրության վերաբերյալ տեղեկությունները կամայական վեբ կայքերին:"</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"հաստատել փաթեթները"</string>
@@ -1727,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"Ընտրեք տարին"</string>
<string name="item_is_selected" msgid="949687401682476608">"Ընտրված է <xliff:g id="ITEM">%1$s</xliff:g> տարրը"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> թիվը ջնջված է"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Աշխատանքային <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 1c80430..666d585 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Pilih tahun"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> dipilih"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> dihapus"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Kantor <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Gunakan lock-to-app?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app mengunci layar dalam satu aplikasi tunggal.\n\nUntuk keluar, tekan lama tombol aplikasi terbaru $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"TIDAK"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"MULAI"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Mulai Lock-to-app"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Keluar dari Lock-to-app"</string>
</resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index d7b7748..3397834 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Seleziona anno"</string>
<string name="item_is_selected" msgid="949687401682476608">"Elemento selezionato: <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> eliminato"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> lavoro"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Utilizzare Lock-to-app?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"La funzione Lock-to-app consente di bloccare la visualizzazione in un\'unica app.\n\nPer uscire, premi e tieni premuto il pulsante delle app recenti $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NO"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"AVVIA"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Avvia Lock-to-app"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Esci da Lock-to-app"</string>
</resources>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 8b33339..f67ae0c 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"בחר שנה"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> נבחר"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> נמחק"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"עבודה <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"האם להשתמש ב\'נעל באפליקציה\'?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"\'נעל באפליקציה\' נועל את המסך באפליקציה יחידה.\n\nכדי לצאת, לחץ על לחצן האפליקציות האחרונות $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"לא"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"הפעל"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"הפעל את \'נעל באפליקציה\'"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"צא מ\'נעל באפליקציה\'"</string>
</resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index a4b95a6..1bd8906 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"インストール済みアラームアプリのアラームを設定することをアプリに許可します。この機能が実装されていないアラームアプリもあります。"</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"ボイスメールの追加"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"ボイスメール受信トレイにメッセージを追加することをアプリに許可します。"</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"すべてのボイスメールの読み取り"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"すべてのボイスメールの読み取りをアプリに許可します。"</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ブラウザの現在地情報に対する権限の変更"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ブラウザの現在地情報に対する権限の変更をアプリに許可します。この許可を悪意のあるアプリに利用されると、任意のウェブサイトに現在地情報が送信される恐れがあります。"</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"パッケージのベリファイ"</string>
@@ -1727,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"年を選択"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g>を選択しました"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g>を削除しました"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"仕事の<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml
index 694e1c7..a7601d2 100644
--- a/core/res/res/values-ka-rGE/strings.xml
+++ b/core/res/res/values-ka-rGE/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"აპს შეეძლება მაღვიძარას დაყენება დაინსტალირებული მაღვიძარას აპლიკაციაში. ამ ფუნქციას მაღვიძარას ზოგიერთი აპი არ იყენებს."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"ხმოვანი ფოსტის დამატება"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"აპს შეეძლება დაამატოს შეტყობინებები თქვენი ხმოვანი ფოსტის შემოსულებში."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"მთელი ხმოვანი ფოსტის წაკითხვა"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"აპს ეძლევა მთელი თქვენი ხმოვანი ფოსტების წაკითხვის უფლება."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ბრაუზერის გეოლოკაციის უფლებების შეცვლა"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"აპს შეეძლება ბრაუზერის გეოლოკაციის უფლებების შეცვლა. მავნე აპებმა ეს შესაძლოა გამოიყენონ ნებისმიერი ვებსაიტისთვის მდებარეობის შესახებ ინფორმაციის გასაგზავნად."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"პაკეტების გადამოწმება"</string>
@@ -1727,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"აირჩიეთ წელი"</string>
<string name="item_is_selected" msgid="949687401682476608">"არჩეულია <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> წაიშალა"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"სამსახური <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml
index 421ff10..d8bf2ff 100644
--- a/core/res/res/values-km-rKH/strings.xml
+++ b/core/res/res/values-km-rKH/strings.xml
@@ -68,7 +68,7 @@
</plurals>
<string name="imei" msgid="2625429890869005782">"IMEI"</string>
<string name="meid" msgid="4841221237681254195">"MEID"</string>
- <string name="ClipMmi" msgid="6952821216480289285">"លេខសម្គាល់អ្នកហៅចូល"</string>
+ <string name="ClipMmi" msgid="6952821216480289285">"លេខសម្គាល់អ្នកហៅចូល"</string>
<string name="ClirMmi" msgid="7784673673446833091">"លេខសម្គាល់អ្នកហៅចេញ"</string>
<string name="CfMmi" msgid="5123218989141573515">"បញ្ជូនការហៅបន្ត"</string>
<string name="CwMmi" msgid="9129678056795016867">"រង់ចាំការហៅ"</string>
@@ -125,7 +125,7 @@
<string name="cfTemplateRegisteredTime" msgid="6781621964320635172">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> ៖ មិនបានបញ្ជូនបន្ត"</string>
<string name="fcComplete" msgid="3118848230966886575">"កូដលក្ខណៈពេញលេញ។"</string>
<string name="fcError" msgid="3327560126588500777">"បញ្ហាការតភ្ជាប់ ឬកូដលក្ខណៈមិនត្រឹមត្រូវ។"</string>
- <string name="httpErrorOk" msgid="1191919378083472204">"យល់ព្រម"</string>
+ <string name="httpErrorOk" msgid="1191919378083472204">"យល់ព្រម"</string>
<string name="httpError" msgid="7956392511146698522">"មានកំហុសបណ្ដាញ។"</string>
<string name="httpErrorLookup" msgid="4711687456111963163">"រកមិនឃើញ URL ។"</string>
<string name="httpErrorUnsupportedAuthScheme" msgid="6299980280442076799">"គ្រោងការណ៍ផ្ទៀងផ្ទាត់តំបន់បណ្ដាញមិនត្រូវបានគាំទ្រ។"</string>
@@ -183,7 +183,7 @@
<string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"បើកសំឡេង"</string>
<string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"ពេលជិះយន្តហោះ"</string>
<string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"បានបើករបៀបពេលជិះយន្តហោះ"</string>
- <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"បានបិទរបៀបពេលជិះយន្តហោះ"</string>
+ <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"បានបិទរបៀបពេលជិះយន្តហោះ"</string>
<string name="global_action_settings" msgid="1756531602592545966">"ការកំណត់"</string>
<string name="status_bar_notification_info_overflow" msgid="5301981741705354993">"999+"</string>
<string name="safeMode" msgid="2788228061547930246">"របៀបសុវត្ថិភាព"</string>
@@ -195,7 +195,7 @@
<string name="permgrouplab_messages" msgid="7521249148445456662">"សាររបស់អ្នក"</string>
<string name="permgroupdesc_messages" msgid="7821999071003699236">"អាន និងសរសេរសារ SMS, អ៊ីមែល និងសារផ្សេងៗទៀតរបស់អ្នក។"</string>
<string name="permgrouplab_personalInfo" msgid="3519163141070533474">"ព័ត៌មានផ្ទាល់ខ្លួនរបស់អ្នក"</string>
- <string name="permgroupdesc_personalInfo" msgid="8426453129788861338">"ចូលដំណើរការព័ត៌មានដោយផ្ទាល់អំពីអ្នក ដែលបានរក្សាទុកក្នុងកាតទំនាក់ទំនងរបស់អ្នក។"</string>
+ <string name="permgroupdesc_personalInfo" msgid="8426453129788861338">"ចូលដំណើរការព័ត៌មានដោយផ្ទាល់អំពីអ្នក ដែលបានរក្សាទុកក្នុងកាតទំនាក់ទំនងរបស់អ្នក។"</string>
<string name="permgrouplab_socialInfo" msgid="5799096623412043791">"ព័ត៌មានសង្គមរបស់អ្នក"</string>
<string name="permgroupdesc_socialInfo" msgid="7129842457611643493">"ចូលដំណើរការព័ត៌មានដោយផ្ទាល់អំពីទំនាក់ទំនង និងការភ្ជាប់សង្គមរបស់អ្នក។"</string>
<string name="permgrouplab_location" msgid="635149742436692049">"ទីតាំងរបស់អ្នក"</string>
@@ -384,7 +384,7 @@
<string name="permdesc_readInputState" msgid="8387754901688728043">"ឲ្យកម្មវិធីមើលគ្រាប់ចុចដែលអ្នកចុចពេលមានអន្តរកម្មជាមួយកម្មវិធីផ្សេង (ដូចជា បញ្ចូលពាក្យសម្ងាត់)។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string>
<string name="permlab_bindInputMethod" msgid="3360064620230515776">"ចងទៅវិធីសាស្ត្របញ្ចូល"</string>
<string name="permdesc_bindInputMethod" msgid="3250440322807286331">"ឲ្យម្ចាស់ចងចំណុចប្រទាក់កម្រិតកំពូលនៃវិធីសាស្ត្របញ្ចូល។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string>
- <string name="permlab_bindAccessibilityService" msgid="5357733942556031593">"ចងសេវាកម្មភាពមធ្យោបាយងាយស្រួល"</string>
+ <string name="permlab_bindAccessibilityService" msgid="5357733942556031593">"ចងសេវាកម្មភាពមធ្យោបាយងាយស្រួល"</string>
<string name="permdesc_bindAccessibilityService" msgid="7034615928609331368">"ឲ្យម្ចាស់ចងចំណុចប្រទាក់កម្រិតកំពូលនៃសេវាកម្មភាពងាយស្រួល។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string>
<string name="permlab_bindPrintService" msgid="8462815179572748761">"ចងសេវាកម្មបោះពុម្ព"</string>
<string name="permdesc_bindPrintService" msgid="7960067623209111135">"ឲ្យម្ចាស់ចងចំណុចប្រទាក់កម្រិតកំពូលនៃសេវាកម្មធាតុក្រាហ្វិក។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string>
@@ -404,7 +404,7 @@
<string name="permdesc_manageVoiceKeyphrases" msgid="8476560722907530008">"អនុញ្ញាតឲ្យម្ចាស់គ្រប់គ្រងឃ្លាសម្រាប់ការរកឃើញពាក្យជាសំឡេង។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string>
<string name="permlab_bindRemoteDisplay" msgid="1782923938029941960">"ភ្ជាប់ទៅការបង្ហាញពីចម្ងាយ"</string>
<string name="permdesc_bindRemoteDisplay" msgid="1261242718727295981">"អនុញ្ញាតឲ្យម្ចាស់ភ្ជាប់ទៅចំណុចប្រទាក់កម្រិតកំពូលនៃការបង្ហាញពីចម្ងាយ។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string>
- <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"ចងសេវាកម្មធាតុក្រាហ្វិក"</string>
+ <string name="permlab_bindRemoteViews" msgid="5697987759897367099">"ចងសេវាកម្មធាតុក្រាហ្វិក"</string>
<string name="permdesc_bindRemoteViews" msgid="4717987810137692572">"ឲ្យម្ចាស់ចងចំណុចប្រទាក់កម្រិតកំពូលនៃសេវាកម្មធាតុក្រាហ្វិក។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string>
<string name="permlab_bindRouteProvider" msgid="4869394607915096847">"ភ្ជាប់ទៅសេវាកម្មក្រុមហ៊ុនផ្ដល់ច្រក"</string>
<string name="permdesc_bindRouteProvider" msgid="4703804520859960329">"អនុញ្ញាតឲ្យម្ចាស់ភ្ជាប់ទៅក្រុមហ៊ុនផ្ដល់ច្រកដែលបានចុះឈ្មោះ។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string>
@@ -412,7 +412,7 @@
<string name="permdesc_bindDeviceAdmin" msgid="569715419543907930">"ឲ្យម្ចាស់ផ្ញើគោលបំណងទៅអ្នកគ្រប់គ្រងឧបករណ៍។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string>
<string name="permlab_bindTvInput" msgid="5601264742478168987">"ភ្ជាប់ទៅការបញ្ចូលទូរទស្សន៍"</string>
<string name="permdesc_bindTvInput" msgid="2371008331852001924">"អនុញ្ញាតឲ្យម្ចាស់ភ្ជាប់ទៅចំណុចប្រទាក់កម្រិតខ្ពស់នៃការបញ្ចូលទូរទស្សន៍។ មិនគួរចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string>
- <string name="permlab_manageDeviceAdmins" msgid="4248828900045808722">"បន្ថែម ឬលុបកម្មវិធីគ្រប់គ្រងឧបករណ៍"</string>
+ <string name="permlab_manageDeviceAdmins" msgid="4248828900045808722">"បន្ថែម ឬលុបកម្មវិធីគ្រប់គ្រងឧបករណ៍"</string>
<string name="permdesc_manageDeviceAdmins" msgid="5025608167709942485">"អនុញ្ញាតឲ្យម្ចាស់បន្ថែម ឬលុបកម្មវិធីគ្រប់គ្រងឧបករណ៍សកម្មចេញ។ មិនគួរប្រើសម្រាប់កម្មវិធីធម្មតាទេ។"</string>
<string name="permlab_setOrientation" msgid="3365947717163866844">"ប្ដូរទិសអេក្រង់"</string>
<string name="permdesc_setOrientation" msgid="3046126619316671476">"ឲ្យកម្មវិធីប្ដូរការបង្វិលអេក្រង់នៅពេលណាមួយ។ មិនចាំបាច់សម្រាប់កម្មវិធីធម្មតាទេ។"</string>
@@ -424,9 +424,9 @@
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"ឲ្យកម្មវិធីស្នើសញ្ញាដែលបានផ្ដល់ត្រូវផ្ញើទៅដំណើរការស្ថិតស្ថេរទាំងអស់។"</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"ធ្វើឲ្យកម្មវិធីដំណើរការជានិច្ច"</string>
<string name="permdesc_persistentActivity" product="tablet" msgid="8525189272329086137">"ឲ្យកម្មវិធីធ្វើជាផ្នែកស្ថិតស្ថេរដោយខ្លួនឯងក្នុងអង្គចងចាំ។ វាអាចកំណត់អង្គចងចាំដែលអាចប្រើបានចំពោះកម្មវិធីផ្សេងៗ ដោយធ្វើឲ្យកុំព្យូទ័របន្ទះយឺត។"</string>
- <string name="permdesc_persistentActivity" product="default" msgid="4384760047508278272">"ឲ្យកម្មវិធី ធ្វើជាផ្នែកអចិន្ត្រៃយ៍នៃខ្លួនក្នុងអង្គចងចាំ។ វាអាចកម្រិតអង្គចងចាំអាចប្រើបាន ដើម្បីធ្វើឲ្យកម្មវិធីផ្សេងធ្វើឲ្យទូរស័ព្ទរបស់អ្នកយឺត។"</string>
+ <string name="permdesc_persistentActivity" product="default" msgid="4384760047508278272">"ឲ្យកម្មវិធី ធ្វើជាផ្នែកអចិន្ត្រៃយ៍នៃខ្លួនក្នុងអង្គចងចាំ។ វាអាចកម្រិតអង្គចងចាំអាចប្រើបាន ដើម្បីធ្វើឲ្យកម្មវិធីផ្សេងធ្វើឲ្យទូរស័ព្ទរបស់អ្នកយឺត។"</string>
<string name="permlab_deletePackages" msgid="184385129537705938">"លុបកម្មវិធី"</string>
- <string name="permdesc_deletePackages" msgid="7411480275167205081">"ឲ្យកម្មវិធីលុបកញ្ចប់ Android ។ កម្មវិធីព្យាបាទអាចប្រើវា ដើម្បីលុបកម្មវិធីសំខាន់ៗ។"</string>
+ <string name="permdesc_deletePackages" msgid="7411480275167205081">"ឲ្យកម្មវិធីលុបកញ្ចប់ Android ។ កម្មវិធីព្យាបាទអាចប្រើវា ដើម្បីលុបកម្មវិធីសំខាន់ៗ។ "</string>
<string name="permlab_clearAppUserData" msgid="274109191845842756">"លុបទិន្នន័យរបស់កម្មវិធីផ្សេង"</string>
<string name="permdesc_clearAppUserData" msgid="4625323684125459488">"ឲ្យកម្មវិធីសម្អាតទិន្នន័យអ្នកប្រើ។"</string>
<string name="permlab_deleteCacheFiles" msgid="3128665571837408675">"លុបឃ្លាំងសម្ងាត់កម្មវិធីផ្សេងៗ"</string>
@@ -477,7 +477,7 @@
<string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"ឲ្យកម្មវិធីកែទិន្នន័យអំពីទំនាក់ទំនងរបស់អ្នកដែលបានរក្សាទុកក្នុងកុំព្យូទ័របន្ទះ រួមមានប្រេកង់ដែលអ្នកបានហៅ អ៊ីមែល ឬទាក់ទងតាមវិធីផ្សេងៗជាមួយទំនាក់ទំនងជាក់លាក់។ សិទ្ធិនេះអនុញ្ញាតឲ្យកម្មវិធីលុបទិន្នន័យទំនាក់ទំនងរបស់អ្នក។"</string>
<string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"ឲ្យកម្មវិធីកែទិន្នន័យអំពីទំនាក់ទំនងរបស់អ្នកដែលបានរក្សាទុកក្នុងទូរស័ព្ទរបស់អ្នក រួមមានប្រេកង់ដែលអ្នកបានហៅ អ៊ីមែល ឬបានទាក់ទងតាមវិធីផ្សេងៗជាមួយទំនាក់ទំនាក់ជាក់លាក់។ សិទ្ធិនេះឲ្យកម្មវិធីលុបទិន្នន័យទំនាក់ទំនង។"</string>
<string name="permlab_readCallLog" msgid="3478133184624102739">"អានកំណត់ហេតុហៅ"</string>
- <string name="permdesc_readCallLog" product="tablet" msgid="3700645184870760285">"ឲ្យកម្មវិធីអានបញ្ជីហៅកុំព្យូទ័របន្ទះរបស់អ្នក រួមមានទិន្នន័យអំពីការហៅចូល និងចេញ។ សិទ្ធិនេះអនុញ្ញាតឲ្យកម្មវិធីរក្សាទុកទិន្នន័យបញ្ជីហៅរបស់អ្នក ហើយកម្មវិធីព្យាបាទអាចចែករំលែកទិន្នន័យបញ្ជីហៅដោយមិនឲ្យអ្នកដឹង។"</string>
+ <string name="permdesc_readCallLog" product="tablet" msgid="3700645184870760285">"ឲ្យកម្មវិធីអានបញ្ជីហៅកុំព្យូទ័របន្ទះរបស់អ្នក រួមមានទិន្នន័យអំពីការហៅចូល និងចេញ។ សិទ្ធិនេះអនុញ្ញាតឲ្យកម្មវិធីរក្សាទុកទិន្នន័យបញ្ជីហៅរបស់អ្នក ហើយកម្មវិធីព្យាបាទអាចចែករំលែកទិន្នន័យបញ្ជីហៅដោយមិនឲ្យអ្នកដឹង។"</string>
<string name="permdesc_readCallLog" product="default" msgid="5777725796813217244">"ឲ្យកម្មវិធីអានបញ្ជីហៅទូរស័ព្ទរបស់អ្នក រួមមានទិន្នន័យអំពីការហៅចូល និងចេញ។ សិទ្ធិនេះអនុញ្ញាតឲ្យកម្មវិធីរក្សាទុកទិន្នន័យបញ្ជីហៅរបស់អ្នក ហើយកម្មវិធីព្យាបាទអាចចែករំលែកទិន្នន័យបញ្ជីហៅដោយមិនឲ្យអ្នកដឹង។"</string>
<string name="permlab_writeCallLog" msgid="8552045664743499354">"សរសេរបញ្ជីហៅ"</string>
<string name="permdesc_writeCallLog" product="tablet" msgid="6661806062274119245">"ឲ្យកម្មវិធីកែបញ្ជីហៅកុំព្យូទ័របន្ទះរបស់អ្នករួមមានទិន្នន័យអំពីការហៅចូល និងចេញ។កម្មវិធីព្យាបាទអាចប្រើវា ដើម្បីលុប ឬកែបញ្ជីហៅរបស់អ្នក។"</string>
@@ -613,7 +613,7 @@
<string name="permdesc_setWallpaperHints" msgid="8235784384223730091">"ឲ្យកម្មវិធីកំណត់ជំនួយទំហំផ្ទាំងរូបភាពប្រព័ន្ធ។"</string>
<string name="permlab_masterClear" msgid="2315750423139697397">"កំណត់ប្រព័ន្ធទៅលំនាំដើមរោងចក្រឡើងវិញ"</string>
<string name="permdesc_masterClear" msgid="3665380492633910226">"ឲ្យកម្មវិធីកំណត់ប្រព័ន្ធដូចការកំណត់ចេញពីរោងចក្រឡើងវិញពេញលេញ ដោយលុបទិន្នន័យ ការកំណត់រចនាសម្ព័ន្ធ និងកម្មវិធីបានដំឡើង។"</string>
- <string name="permlab_setTime" msgid="2021614829591775646">"កំណត់ម៉ោង"</string>
+ <string name="permlab_setTime" msgid="2021614829591775646">"កំណត់ម៉ោង"</string>
<string name="permdesc_setTime" product="tablet" msgid="1896341438151152881">"ឲ្យកម្មវិធីប្ដូរម៉ោងកុំព្យូទ័របន្ទះ។"</string>
<string name="permdesc_setTime" product="default" msgid="1855702730738020">"ឲ្យកម្មវិធីប្ដូរម៉ោងទូរស័ព្ទ។"</string>
<string name="permlab_setTimeZone" msgid="2945079801013077340">"កំណត់តំបន់ពេលវេលា"</string>
@@ -779,7 +779,7 @@
<string-array name="organizationTypes">
<item msgid="7546335612189115615">"កន្លែងធ្វើការ"</item>
<item msgid="4378074129049520373">"ផ្សេងៗ"</item>
- <item msgid="3455047468583965104">"តាមតម្រូវការ"</item>
+ <item msgid="3455047468583965104">"តាមតម្រូវការ"</item>
</string-array>
<string-array name="imProtocols">
<item msgid="8595261363518459565">"AIM"</item>
@@ -795,7 +795,7 @@
<string name="phoneTypeHome" msgid="2570923463033985887">"ផ្ទះ"</string>
<string name="phoneTypeMobile" msgid="6501463557754751037">"ចល័ត"</string>
<string name="phoneTypeWork" msgid="8863939667059911633">"កន្លែងធ្វើការ"</string>
- <string name="phoneTypeFaxWork" msgid="3517792160008890912">"ទូរសារកន្លែងធ្វើការ"</string>
+ <string name="phoneTypeFaxWork" msgid="3517792160008890912">"ទូរសារកន្លែងធ្វើការ"</string>
<string name="phoneTypeFaxHome" msgid="2067265972322971467">"ទូរសារផ្ទះ"</string>
<string name="phoneTypePager" msgid="7582359955394921732">"ភេយ័រ"</string>
<string name="phoneTypeOther" msgid="1544425847868765990">"ផ្សេងៗ"</string>
@@ -920,7 +920,7 @@
<string name="lockscreen_glogin_too_many_attempts" msgid="2751368605287288808">"ព្យាយាមលំនាំច្រើនពេក"</string>
<string name="lockscreen_glogin_instructions" msgid="3931816256100707784">"ដើម្បីដោះសោ ចូលគណនី Google របស់អ្នក។"</string>
<string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"ឈ្មោះអ្នកប្រើ (អ៊ីមែល)"</string>
- <string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"ពាក្យសម្ងាត់"</string>
+ <string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"ពាក្យសម្ងាត់"</string>
<string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"ចូល"</string>
<string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"ឈ្មោះអ្នកប្រើ ឬពាក្យសម្ងាត់មិនត្រឹមត្រូវ។"</string>
<string name="lockscreen_glogin_account_recovery_hint" msgid="1696924763690379073">"ភ្លេចឈ្មោះអ្នកប្រើ ឬពាក្យសម្ងាត់របស់អ្នក?\nមើល "<b>"google.com/accounts/recovery"</b>" ។"</string>
@@ -965,7 +965,7 @@
<string name="factorytest_failed" msgid="5410270329114212041">"បានបរាជ័យក្នុងការសាកល្បងរោងចក្រ"</string>
<string name="factorytest_not_system" msgid="4435201656767276723">"សកម្មភាព FACTORY_TEST ត្រូវបានគាំទ្រសម្រាប់តែកញ្ចប់បានដំឡើងក្នុង /system/app."</string>
<string name="factorytest_no_action" msgid="872991874799998561">"រកមិនឃើញកញ្ចប់ដែលផ្ដល់សកម្មភាព FACTORY_TEST ។"</string>
- <string name="factorytest_reboot" msgid="6320168203050791643">"ចាប់ផ្ដើមឡើងវិញ"</string>
+ <string name="factorytest_reboot" msgid="6320168203050791643">"ចាប់ផ្ដើមឡើងវិញ"</string>
<string name="js_dialog_title" msgid="1987483977834603872">"ទំព័រមានចំណងជើង \"<xliff:g id="TITLE">%s</xliff:g>\" សរសេរ៖"</string>
<string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string>
<string name="js_dialog_before_unload_title" msgid="2619376555525116593">"បញ្ជាក់ការរុករក"</string>
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"ឲ្យកម្មវិធីកំណត់សំឡេងរោទ៍ក្នុងកម្មវិធីនាឡិការោទ៍បានដំឡើង។ កម្មវិធីនាឡិការោទ៍មួយចំនួនអាចមិនអនុវត្តលក្ខណៈនេះ។"</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"បន្ថែមសារជាសំឡេង"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"ឲ្យកម្មវិធីបន្ថែមសារទៅប្រអប់ទទួលសារជាសំឡេងរបស់អ្នក។"</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"អានសារជាសំឡេងទាំងអស់"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"ឲ្យកម្មវិធីអានសារជាសំឡេងរបស់អ្នកទាំងអស់។"</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"កែសិទ្ធិទីតាំងភូមិសាស្ត្ររបស់កម្មវិធីអ៊ីនធឺណិត"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"ឲ្យកម្មវិធីកែសិទ្ធិទីតាំងភូមិសាស្ត្ររបស់កម្មវិធីអ៊ីនធឺណិត។ កម្មវិធីព្យាបាទអាចប្រើវា ដើម្បីឲ្យផ្ញើព័ត៌មានទីតាំងទៅតំបន់បណ្ដាញដោយបំពាន។"</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"ផ្ទៀងផ្ទាត់កញ្ចប់"</string>
@@ -1027,7 +1025,7 @@
<string name="prepend_shortcut_label" msgid="2572214461676015642">"ម៉ឺនុយ +"</string>
<string name="menu_space_shortcut_label" msgid="2410328639272162537">"ដកឃ្លា"</string>
<string name="menu_enter_shortcut_label" msgid="2743362785111309668">"enter"</string>
- <string name="menu_delete_shortcut_label" msgid="3658178007202748164">"លុប"</string>
+ <string name="menu_delete_shortcut_label" msgid="3658178007202748164">"លុប"</string>
<string name="search_go" msgid="8298016669822141719">"ស្វែងរក"</string>
<string name="searchview_description_search" msgid="6749826639098512120">"ស្វែងរក"</string>
<string name="searchview_description_query" msgid="5911778593125355124">"ស្វែងរកសំណួរ"</string>
@@ -1111,18 +1109,18 @@
<string name="preposition_for_date" msgid="9093949757757445117">"នៅ <xliff:g id="DATE">%s</xliff:g>"</string>
<string name="preposition_for_time" msgid="5506831244263083793">"នៅម៉ោង <xliff:g id="TIME">%s</xliff:g>"</string>
<string name="preposition_for_year" msgid="5040395640711867177">"ក្នុងឆ្នាំ <xliff:g id="YEAR">%s</xliff:g>"</string>
- <string name="day" msgid="8144195776058119424">"ថ្ងៃ"</string>
+ <string name="day" msgid="8144195776058119424">"ថ្ងៃ"</string>
<string name="days" msgid="4774547661021344602">"ថ្ងៃ"</string>
<string name="hour" msgid="2126771916426189481">"ម៉ោង"</string>
<string name="hours" msgid="894424005266852993">"ម៉ោង"</string>
- <string name="minute" msgid="9148878657703769868">"នាទី"</string>
+ <string name="minute" msgid="9148878657703769868">"នាទី"</string>
<string name="minutes" msgid="5646001005827034509">"នាទី"</string>
- <string name="second" msgid="3184235808021478">"វិនាទី"</string>
+ <string name="second" msgid="3184235808021478">"វិនាទី"</string>
<string name="seconds" msgid="3161515347216589235">"វិនាទី"</string>
- <string name="week" msgid="5617961537173061583">"សប្ដាហ៍"</string>
- <string name="weeks" msgid="6509623834583944518">"សប្ដាហ៍"</string>
- <string name="year" msgid="4001118221013892076">"ឆ្នាំ"</string>
- <string name="years" msgid="6881577717993213522">"ឆ្នាំ"</string>
+ <string name="week" msgid="5617961537173061583">"សប្ដាហ៍"</string>
+ <string name="weeks" msgid="6509623834583944518">"សប្ដាហ៍"</string>
+ <string name="year" msgid="4001118221013892076">"ឆ្នាំ"</string>
+ <string name="years" msgid="6881577717993213522">"ឆ្នាំ"</string>
<plurals name="duration_seconds">
<item quantity="one" msgid="6962015528372969481">"1 វិនាទី"</item>
<item quantity="other" msgid="1886107766577166786">"<xliff:g id="COUNT">%d</xliff:g> វិនាទី"</item>
@@ -1138,12 +1136,12 @@
<string name="VideoView_error_title" msgid="3534509135438353077">"បញ្ហាវីដេអូ"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="3186670335938670444">"វីដេអូនេះមិនត្រឹមត្រូវសម្រាប់ចរន្តចូលឧបករណ៍នេះ។"</string>
<string name="VideoView_error_text_unknown" msgid="3450439155187810085">"មិនអាចចាក់វីដេអូនេះ។"</string>
- <string name="VideoView_error_button" msgid="2822238215100679592">"យល់ព្រម"</string>
+ <string name="VideoView_error_button" msgid="2822238215100679592">"យល់ព្រម"</string>
<string name="relative_time" msgid="1818557177829411417">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="noon" msgid="7245353528818587908">"រសៀល"</string>
<string name="Noon" msgid="3342127745230013127">"រសៀល"</string>
<string name="midnight" msgid="7166259508850457595">"កណ្ដាលអធ្រាត្រ"</string>
- <string name="Midnight" msgid="5630806906897892201">"កណ្ដាលអធ្រាត្រ"</string>
+ <string name="Midnight" msgid="5630806906897892201">"កណ្ដាលអធ្រាត្រ"</string>
<string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string>
<string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
<string name="selectAll" msgid="6876518925844129331">"ជ្រើសទាំងអស់"</string>
@@ -1160,13 +1158,13 @@
<string name="inputMethod" msgid="1653630062304567879">"វិធីសាស្ត្របញ្ចូល"</string>
<string name="editTextMenuTitle" msgid="4909135564941815494">"សកម្មភាពអត្ថបទ"</string>
<string name="low_internal_storage_view_title" msgid="5576272496365684834">"អស់ទំហំផ្ទុក"</string>
- <string name="low_internal_storage_view_text" msgid="6640505817617414371">"មុខងារប្រព័ន្ធមួយចំនួនអាចមិនដំណើរការ"</string>
+ <string name="low_internal_storage_view_text" msgid="6640505817617414371">"មុខងារប្រព័ន្ធមួយចំនួនអាចមិនដំណើរការ"</string>
<string name="app_running_notification_title" msgid="8718335121060787914">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងដំណើរការ"</string>
<string name="app_running_notification_text" msgid="4653586947747330058">"ប៉ះ ដើម្បីមើលព័ត៌មានបន្ថែម ឬបញ្ឈប់កម្មវិធី។"</string>
- <string name="ok" msgid="5970060430562524910">"យល់ព្រម"</string>
- <string name="cancel" msgid="6442560571259935130">"បោះបង់"</string>
- <string name="yes" msgid="5362982303337969312">"យល់ព្រម"</string>
- <string name="no" msgid="5141531044935541497">"បោះបង់"</string>
+ <string name="ok" msgid="5970060430562524910">"យល់ព្រម"</string>
+ <string name="cancel" msgid="6442560571259935130">"បោះបង់"</string>
+ <string name="yes" msgid="5362982303337969312">"យល់ព្រម"</string>
+ <string name="no" msgid="5141531044935541497">"បោះបង់"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"ប្រយ័ត្ន"</string>
<string name="loading" msgid="7933681260296021180">"កំពុងផ្ទុក..."</string>
<string name="capital_on" msgid="1544682755514494298">"បើក"</string>
@@ -1175,7 +1173,7 @@
<string name="whichHomeApplication" msgid="4616420172727326782">"ជ្រើសកម្មវិធីដើម"</string>
<string name="alwaysUse" msgid="4583018368000610438">"ប្រើតាមលំនាំដើមសម្រាប់សកម្មភាពនេះ។"</string>
<string name="clearDefaultHintMsg" msgid="3252584689512077257">"សម្អាតលំនាំដើមក្នុងការកំណត់ប្រព័ន្ធ > កម្មវិធី > ទាញយក។"</string>
- <string name="chooseActivity" msgid="7486876147751803333">"ជ្រើសសកម្មភាព"</string>
+ <string name="chooseActivity" msgid="7486876147751803333">"ជ្រើសសកម្មភាព"</string>
<string name="chooseUsbActivity" msgid="6894748416073583509">"ជ្រើសកម្មវិធីសម្រាប់ឧបករណ៍យូអេសប៊ី"</string>
<string name="noApplications" msgid="2991814273936504689">"គ្មានកម្មវិធីអាចអនុវត្តសកម្មភាពនេះ។"</string>
<string name="aerr_title" msgid="1905800560317137752"></string>
@@ -1186,7 +1184,7 @@
<string name="anr_activity_process" msgid="5776209883299089767">"សកម្មភាព <xliff:g id="ACTIVITY">%1$s</xliff:g> មិនឆ្លើយតប។\n\nតើអ្នកចង់បិទវា?"</string>
<string name="anr_application_process" msgid="8941757607340481057">"<xliff:g id="APPLICATION">%1$s</xliff:g> មិនឆ្លើយតប។ តើអ្នកចង់បិទវា?"</string>
<string name="anr_process" msgid="6513209874880517125">"ដំណើរការ <xliff:g id="PROCESS">%1$s</xliff:g> មិនឆ្លើយតប។ \n\nតើអ្នកចង់បិទវាឬ?"</string>
- <string name="force_close" msgid="8346072094521265605">"យល់ព្រម"</string>
+ <string name="force_close" msgid="8346072094521265605">"យល់ព្រម"</string>
<string name="report" msgid="4060218260984795706">"រាយការណ៍"</string>
<string name="wait" msgid="7147118217226317732">"រង់ចាំ"</string>
<string name="webpage_unresponsive" msgid="3272758351138122503">"ទំព័រក្លាយជាមិនឆ្លើយតប។\n\nតើអ្នកចង់បិទវា?"</string>
@@ -1268,19 +1266,19 @@
<string name="sms_short_code_details" msgid="3492025719868078457"><font fgcolor="#ffffb060">"នេះអាចកាត់លុយ"</font>" លើគណនីចល័តរបស់អ្នក។"</string>
<string name="sms_premium_short_code_details" msgid="5523826349105123687"><font fgcolor="#ffffb060">"វានឹងគិតថ្លៃសេវាកម្មលើគណនីចល័តរបស់អ្នក។"</font></string>
<string name="sms_short_code_confirm_allow" msgid="4458878637111023413">"ផ្ញើ"</string>
- <string name="sms_short_code_confirm_deny" msgid="2927389840209170706">"បោះបង់"</string>
+ <string name="sms_short_code_confirm_deny" msgid="2927389840209170706">"បោះបង់"</string>
<string name="sms_short_code_remember_choice" msgid="5289538592272218136">"ចងចាំជម្រើសរបស់ខ្ញុំ"</string>
<string name="sms_short_code_remember_undo_instruction" msgid="4960944133052287484">"អ្នកអាចប្ដូរវាពេលក្រោយក្នុងការកំណត់ > កម្មវិធី"</string>
<string name="sms_short_code_confirm_always_allow" msgid="3241181154869493368">"អនុញ្ញាតជានិច្ច"</string>
<string name="sms_short_code_confirm_never_allow" msgid="446992765774269673">"កុំអនុញ្ញាត"</string>
<string name="sim_removed_title" msgid="6227712319223226185">"បានដកស៊ីមកាតចេញ"</string>
- <string name="sim_removed_message" msgid="2333164559970958645">"បណ្ដាញចល័តនឹងប្រើលែងបានរហូតដល់អ្នកចាប់ផ្ដើមជាមួយស៊ីមកាតដែលបាបញ្ចូលត្រឹមត្រូវ។"</string>
+ <string name="sim_removed_message" msgid="2333164559970958645">"បណ្ដាញចល័តនឹងប្រើលែងបានរហូតដល់អ្នកចាប់ផ្ដើមជាមួយស៊ីមកាតដែលបាបញ្ចូលត្រឹមត្រូវ។"</string>
<string name="sim_done_button" msgid="827949989369963775">"រួចរាល់"</string>
<string name="sim_added_title" msgid="3719670512889674693">"បានបន្ថែមស៊ីមកាត"</string>
<string name="sim_added_message" msgid="6599945301141050216">"ចាប់ផ្ដើមឧបករណ៍របស់អ្នកឡើងវិញ ដើម្បីចូលដំណើរការបណ្ដាញចល័ត។"</string>
<string name="sim_restart_button" msgid="4722407842815232347">"ចាប់ផ្ដើមឡើងវិញ"</string>
- <string name="time_picker_dialog_title" msgid="8349362623068819295">"កំណត់ម៉ោង"</string>
- <string name="date_picker_dialog_title" msgid="5879450659453782278">"កំណត់កាលបរិច្ឆេទ"</string>
+ <string name="time_picker_dialog_title" msgid="8349362623068819295">"កំណត់ម៉ោង"</string>
+ <string name="date_picker_dialog_title" msgid="5879450659453782278">"កំណត់កាលបរិច្ឆេទ"</string>
<string name="date_time_set" msgid="5777075614321087758">"កំណត់"</string>
<string name="date_time_done" msgid="2507683751759308828">"រួចរាល់"</string>
<string name="perms_new_perm_prefix" msgid="8257740710754301407"><font size="12" fgcolor="#ff33b5e5">"ថ្មី៖ "</font></string>
@@ -1358,7 +1356,7 @@
<string name="permdesc_copyProtectedData" msgid="4390697124288317831">"ឲ្យកម្មវិធីដកសេវាកម្មនៃកម្មវិធីផ្ទុកលំនាំដើម ដើម្បីចម្លងមាតិកា។ មិនសម្រាប់ប្រើដោយកម្មវិធីលំនាំដើម។"</string>
<string name="permlab_route_media_output" msgid="1642024455750414694">"នាំផ្លូវលទ្ធផលមេឌៀ"</string>
<string name="permdesc_route_media_output" msgid="4932818749547244346">"ឲ្យកម្មវិធីនាំផ្លូវលទ្ធផលមេឌៀទៅឧបករណ៍ខាងក្រៅផ្សេង។"</string>
- <string name="permlab_access_keyguard_secure_storage" msgid="7565552237977815047">"ចូលដំណើរការឧបករណ៍ផ្ទុកសុវត្ថិភាព"</string>
+ <string name="permlab_access_keyguard_secure_storage" msgid="7565552237977815047">"ចូលដំណើរការឧបករណ៍ផ្ទុកសុវត្ថិភាព"</string>
<string name="permdesc_access_keyguard_secure_storage" msgid="5866245484303285762">"ឲ្យកម្មវិធីចូលការផ្ទុកមានសុវត្ថិភាព keguard ។"</string>
<string name="permlab_control_keyguard" msgid="172195184207828387">"ពិនិត្យការបង្ហាញ និងលាក់ការការពារ"</string>
<string name="permdesc_control_keyguard" msgid="3043732290518629061">"ឲ្យកម្មវិធីគ្រប់គ្រង keguard ។"</string>
@@ -1377,7 +1375,7 @@
<string name="ime_action_go" msgid="8320845651737369027">"ទៅ"</string>
<string name="ime_action_search" msgid="658110271822807811">"ស្វែងរក"</string>
<string name="ime_action_send" msgid="2316166556349314424">"ផ្ញើ"</string>
- <string name="ime_action_next" msgid="3138843904009813834">"បន្ទាប់"</string>
+ <string name="ime_action_next" msgid="3138843904009813834">"បន្ទាប់"</string>
<string name="ime_action_done" msgid="8971516117910934605">"រួចរាល់"</string>
<string name="ime_action_previous" msgid="1443550039250105948">"មុន"</string>
<string name="ime_action_default" msgid="2840921885558045721">"អនុវត្ត"</string>
@@ -1386,7 +1384,7 @@
<string name="grant_credentials_permission_message_header" msgid="2106103817937859662">"កម្មវិធីមួយ ឬច្រើនដូចខាងក្រោមស្នើសិទ្ធិ ដើម្បីចូលគណនីរបស់អ្នកឥឡូវ និងពេលអនាគត។"</string>
<string name="grant_credentials_permission_message_footer" msgid="3125211343379376561">"តើអ្នកចង់អនុញ្ញាតសំណើនេះ?"</string>
<string name="grant_permissions_header_text" msgid="6874497408201826708">"ស្នើចូល"</string>
- <string name="allow" msgid="7225948811296386551">"អនុញ្ញាត"</string>
+ <string name="allow" msgid="7225948811296386551">"អនុញ្ញាត"</string>
<string name="deny" msgid="2081879885755434506">"បដិសេធ"</string>
<string name="permission_request_notification_title" msgid="6486759795926237907">"បានស្នើសិទ្ធិ"</string>
<string name="permission_request_notification_with_subtitle" msgid="8530393139639560189">"បានស្នើសិទ្ធិ\nសម្រាប់គណនី <xliff:g id="ACCOUNT">%s</xliff:g> ។"</string>
@@ -1409,12 +1407,12 @@
<string name="no_file_chosen" msgid="6363648562170759465">"គ្មានឯកសារបានជ្រើស"</string>
<string name="reset" msgid="2448168080964209908">"កំណត់ឡើងវិញ"</string>
<string name="submit" msgid="1602335572089911941">"ដាក់ស្នើ"</string>
- <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"បានបើករបៀបរថយន្ត"</string>
+ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"បានបើករបៀបរថយន្ត"</string>
<string name="car_mode_disable_notification_message" msgid="8035230537563503262">"ប៉ះ ដើម្បីចេញពីរបៀបរថយន្ត។"</string>
<string name="tethered_notification_title" msgid="3146694234398202601">"ភ្ជាប់ ឬហតស្ពតសកម្ម"</string>
<string name="tethered_notification_message" msgid="6857031760103062982">"ប៉ះ ដើម្បីរៀបចំ។"</string>
<string name="back_button_label" msgid="2300470004503343439">"ថយក្រោយ"</string>
- <string name="next_button_label" msgid="1080555104677992408">"បន្ទាប់"</string>
+ <string name="next_button_label" msgid="1080555104677992408">"បន្ទាប់"</string>
<string name="skip_button_label" msgid="1275362299471631819">"រំលង"</string>
<string name="throttle_warning_notification_title" msgid="4890894267454867276">"ការប្រើទិន្នន័យចល័តខ្ពស់"</string>
<string name="throttle_warning_notification_message" msgid="3340822228599337743">"ប៉ះ ដើម្បីស្វែងយល់បន្ថែមអំពីការប្រើទិន្នន័យចល័ត។"</string>
@@ -1440,7 +1438,7 @@
<string name="media_shared" product="nosdcard" msgid="5830814349250834225">"ឧបករណ៍ផ្ទុកយូអេសប៊ីបច្ចុប្បន្នកំពុងប្រើដោយកុំព្យូទ័រ។"</string>
<string name="media_shared" product="default" msgid="5706130568133540435">"បច្ចុប្បន្នកាតអេសឌីកំពុងប្រើដោយកុំព្យូទ័រ"</string>
<string name="media_unknown_state" msgid="729192782197290385">"មិនស្គាល់ស្ថានភាពមេឌៀខាងក្រៅ។"</string>
- <string name="share" msgid="1778686618230011964">"ចែករំលែក"</string>
+ <string name="share" msgid="1778686618230011964">"ចែករំលែក"</string>
<string name="find" msgid="4808270900322985960">"រក"</string>
<string name="websearch" msgid="4337157977400211589">"ស្វែងរកតាមបណ្ដាញ"</string>
<string name="find_next" msgid="5742124618942193978">"រកបន្ទាប់"</string>
@@ -1456,7 +1454,7 @@
<string name="sync_undo_deletes" msgid="2941317360600338602">"មិនធ្វើការលុបវិញ"</string>
<string name="sync_do_nothing" msgid="3743764740430821845">"មិនធ្វើអ្វីទេឥឡូវ"</string>
<string name="choose_account_label" msgid="5655203089746423927">"ជ្រើសគណនី"</string>
- <string name="add_account_label" msgid="2935267344849993553">"បន្ថែមគណនីថ្មី"</string>
+ <string name="add_account_label" msgid="2935267344849993553">"បន្ថែមគណនីថ្មី"</string>
<string name="add_account_button_label" msgid="3611982894853435874">"បន្ថែមគណនី"</string>
<string name="number_picker_increment_button" msgid="2412072272832284313">"បង្កើន"</string>
<string name="number_picker_decrement_button" msgid="476050778386779067">"បន្ថយ"</string>
@@ -1475,15 +1473,15 @@
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"បង្កើនឆ្នាំ"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"បន្ថយឆ្នាំ"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
- <string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"បោះបង់"</string>
+ <string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"បោះបង់"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"លុប"</string>
<string name="keyboardview_keycode_done" msgid="1992571118466679775">"រួចរាល់"</string>
<string name="keyboardview_keycode_mode_change" msgid="4547387741906537519">"ប្ដូររបៀប"</string>
<string name="keyboardview_keycode_shift" msgid="2270748814315147690">"Shift"</string>
<string name="keyboardview_keycode_enter" msgid="2985864015076059467">"Enter"</string>
- <string name="activitychooserview_choose_application" msgid="2125168057199941199">"ជ្រើសកម្មវិធី"</string>
+ <string name="activitychooserview_choose_application" msgid="2125168057199941199">"ជ្រើសកម្មវិធី"</string>
<string name="activitychooserview_choose_application_error" msgid="8624618365481126668">"មិនអាចចាប់ផ្ដើម <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
- <string name="shareactionprovider_share_with" msgid="806688056141131819">"ចែករំលែកជាមួយ"</string>
+ <string name="shareactionprovider_share_with" msgid="806688056141131819">"ចែករំលែកជាមួយ"</string>
<string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"ចែករំលែកជាមួយ <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
<string name="content_description_sliding_handle" msgid="415975056159262248">"គ្រប់គ្រងការរុញ។ ប៉ះ & សង្កត់។"</string>
<string name="description_target_unlock_tablet" msgid="3833195335629795055">"អូស ដើម្បីដោះសោ។"</string>
@@ -1497,7 +1495,7 @@
<string name="storage_internal" msgid="4891916833657929263">"ឧបករណ៍ផ្ទុកខាងក្នុង"</string>
<string name="storage_sd_card" msgid="3282948861378286745">"កាតអេសឌី"</string>
<string name="storage_usb" msgid="3017954059538517278">"ឧបករណ៍ផ្ទុកយូអេសប៊ី"</string>
- <string name="extract_edit_menu_button" msgid="8940478730496610137">"កែសម្រួល"</string>
+ <string name="extract_edit_menu_button" msgid="8940478730496610137">"កែសម្រួល"</string>
<string name="data_usage_warning_title" msgid="1955638862122232342">"ការព្រមានប្រើទិន្នន័យ"</string>
<string name="data_usage_warning_body" msgid="2814673551471969954">"ប៉ះ ដើម្បីមើលការប្រើ និងការកំណត់។"</string>
<string name="data_usage_3g_limit_title" msgid="7093334419518706686">"បានបិទទិន្នន័យ 2G-3G"</string>
@@ -1554,7 +1552,7 @@
<string name="media_route_status_available" msgid="6983258067194649391">"ទំនេរ"</string>
<string name="media_route_status_not_available" msgid="6739899962681886401">"មិនទំនេរ"</string>
<string name="media_route_status_in_use" msgid="4533786031090198063">"កំពុងប្រើ"</string>
- <string name="display_manager_built_in_display_name" msgid="2583134294292563941">"អេក្រង់ជាប់"</string>
+ <string name="display_manager_built_in_display_name" msgid="2583134294292563941">"អេក្រង់ជាប់"</string>
<string name="display_manager_hdmi_display_name" msgid="1555264559227470109">"អេក្រង់ HDMI"</string>
<string name="display_manager_overlay_display_name" msgid="5142365982271620716">"#<xliff:g id="ID">%1$d</xliff:g> ត្រួតគ្នា"</string>
<string name="display_manager_overlay_display_title" msgid="652124517672257172">"<xliff:g id="NAME">%1$s</xliff:g>: <xliff:g id="WIDTH">%2$d</xliff:g>x<xliff:g id="HEIGHT">%3$d</xliff:g>, <xliff:g id="DPI">%4$d</xliff:g> dpi"</string>
@@ -1586,7 +1584,7 @@
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"ព្យាយាមលំនាំច្រើនពេក"</string>
<string name="kg_login_instructions" msgid="1100551261265506448">"ដើម្បីដោះសោ ចូលក្នុងគណនី Google ។"</string>
<string name="kg_login_username_hint" msgid="5718534272070920364">"ឈ្មោះអ្នកប្រើ (អ៊ីម៉ែល)"</string>
- <string name="kg_login_password_hint" msgid="9057289103827298549">"ពាក្យសម្ងាត់"</string>
+ <string name="kg_login_password_hint" msgid="9057289103827298549">"ពាក្យសម្ងាត់"</string>
<string name="kg_login_submit_button" msgid="5355904582674054702">"ចូល"</string>
<string name="kg_login_invalid_input" msgid="5754664119319872197">"ឈ្មោះអ្នកប្រើ ឬពាក្យសម្ងាត់មិនត្រឹមត្រូវ។"</string>
<string name="kg_login_account_recovery_hint" msgid="5690709132841752974">"ភ្លេចឈ្មោះអ្នកប្រើ ឬពាក្យសម្ងាត់របស់អ្នក?\nមើល "<b>"google.com/accounts/recovery"</b>" ។"</string>
@@ -1695,7 +1693,7 @@
<string name="mediasize_japanese_you4" msgid="2091777168747058008">"You4"</string>
<string name="mediasize_unknown_portrait" msgid="3088043641616409762">"មិនស្គាល់បញ្ឈរ"</string>
<string name="mediasize_unknown_landscape" msgid="4876995327029361552">"មិនស្គាល់ទេសភាព"</string>
- <string name="write_fail_reason_cancelled" msgid="7091258378121627624">"បានបោះបង់"</string>
+ <string name="write_fail_reason_cancelled" msgid="7091258378121627624">"បានបោះបង់"</string>
<string name="write_fail_reason_cannot_write" msgid="8132505417935337724">"កំហុសក្នុងការសរសេរមាតិកា"</string>
<string name="reason_unknown" msgid="6048913880184628119">"មិនស្គាល់"</string>
<string name="reason_service_unavailable" msgid="7824008732243903268">"មិនបានបើកសេវាកម្មបោះពុម្ព"</string>
@@ -1727,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"ជ្រើសឆ្នាំ"</string>
<string name="item_is_selected" msgid="949687401682476608">"បានជ្រើស <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"បានលុប <xliff:g id="KEY">%1$s</xliff:g>"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"កន្លែងធ្វើការ <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"ប្រើការចាក់សោកម្មវិធី?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"ចាក់សោកម្មវិធីគឺចាក់សោការបង្ហាញក្នុងកម្មវិធីតែមួយ។\n\nដើម្បីចាកចេញ ចុច និងសង្កត់ប៊ូតុងកម្មវិធីថ្មីៗ $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"ទេ"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"ចាប់ផ្ដើម"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"ចាប់ផ្ដើមការចាក់សោកម្មវិធី"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"ចេញពីការចាក់សោកម្មវិធី"</string>
</resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index cffe6bb..d2fd2b0 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"앱이 설치된 알람 시계 앱에서 알람을 설정할 수 있도록 허용합니다. 일부 알람 시계 앱에는 이 기능이 구현되지 않을 수 있습니다."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"음성사서함 추가"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"앱이 음성사서함에 메시지를 추가할 수 있도록 허용합니다."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"모든 음성사서함 읽기"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"앱이 모든 음성사서함을 읽을 수 있도록 허용합니다."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"브라우저 위치 정보 권한 수정"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"앱이 브라우저의 위치 정보 권한을 수정할 수 있도록 허용합니다. 이 경우 악성 앱이 이 기능을 이용하여 임의의 웹사이트에 위치 정보를 보낼 수 있습니다."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"패키지 확인"</string>
@@ -1727,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"연도 선택"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g>이(가) 선택됨"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> 삭제됨"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"업무용 <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml
index b44c9e0..8de71f9 100644
--- a/core/res/res/values-lo-rLA/strings.xml
+++ b/core/res/res/values-lo-rLA/strings.xml
@@ -1705,7 +1705,7 @@
<string name="restr_pin_enter_old_pin" msgid="1462206225512910757">"PIN ປະຈຸບັນ"</string>
<string name="restr_pin_enter_new_pin" msgid="5959606691619959184">"ລະຫັດ PIN ໃໝ່"</string>
<string name="restr_pin_confirm_pin" msgid="8501523829633146239">"ຢືນຢັນລະຫັດ PIN ໃໝ່"</string>
- <string name="restr_pin_create_pin" msgid="8017600000263450337">"ສ້າງ PIN ສໍາລັບການປັບປຸງຂໍ້ຈໍາກັດ"</string>
+ <string name="restr_pin_create_pin" msgid="8017600000263450337">"ສ້າງ PIN ສໍາລັບການປັບປຸງຂໍ້ຈໍາກັດ"</string>
<string name="restr_pin_error_doesnt_match" msgid="2224214190906994548">"PIN ບໍ່ກົງກັນ. ລອງໃໝ່ອີກຄັ້ງ."</string>
<string name="restr_pin_error_too_short" msgid="8173982756265777792">"PIN ສັ້ນເກີນໄປ. ຕ້ອງມີຢ່າງໜ້ອຍ 4 ຫຼັກ."</string>
<plurals name="restr_pin_countdown">
@@ -1725,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"ເລືອກປີ"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> ຖືກເລືອກແລ້ວ"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> ຖືກລຶບແລ້ວ"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"ບ່ອນເຮັດວຽກ <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 82548a0..dcac639 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Pasirinkite metus"</string>
<string name="item_is_selected" msgid="949687401682476608">"Pasirinkta: <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"Ištrinta: <xliff:g id="KEY">%1$s</xliff:g>"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Darbo <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Naudoti programos užrakinimo funkciją?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Naudojant programos užrakinimo funkciją ekrane užrakinama viena programa.\n\nJei norite išeiti, paspauskite ir palaikykite paspaudę naujausių programų mygtuką $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NE"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"ĮJUNGTI"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Įjungti programos užrakinimo funkciją"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Išeiti iš programos užrakinimo funkcijos"</string>
</resources>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 798a39d..a9a9984 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"Ļauj lietotnei iestatīt signālu instalētajā modinātājpulksteņa lietotnē. Dažās modinātājpulksteņu lietotnēs šo funkciju, iespējams, nevar ieviest."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"pievienot balss pastu"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Ļauj lietotnei pievienot ziņojumus jūsu balss pasta iesūtnei."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"lasīt visus balss pasta ziņojumus"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Ļauj lietotnei lasīt visus jūsu balss pasta ziņojumus."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"pārveidot pārlūkprogrammas ģeogrāfiskās atrašanās vietas atļaujas"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Ļauj lietotnei modificēt pārlūkprogrammas ģeogrāfiskās atrašanās vietas atļaujas. Ļaunprātīgas lietotnes to var izmantot, lai atļautu atrašanās vietas informācijas sūtīšanu uz citām vietnēm."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"pakotņu verificēšana"</string>
@@ -1727,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Atlasiet gadu."</string>
<string name="item_is_selected" msgid="949687401682476608">"Atlasīts: <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> tika dzēsts."</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Darbā: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Vai izmantot bloķēšanu darbībai vienā lietotnē?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Konfigurācija \"Bloķēšana darbībai vienā lietotnē\" nobloķē ekrānu vienā lietotnē.\n\nLai izietu, nospiediet un turiet pogu “Pēdējās izmantotās lietotnes” $."</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NĒ"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"SĀKT"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Sākt bloķēšanu darbībai vienā lietotnē"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Iziet no bloķēšanas darbībai vienā lietotnē"</string>
</resources>
diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml
index d19d607..c55bbcd 100644
--- a/core/res/res/values-mn-rMN/strings.xml
+++ b/core/res/res/values-mn-rMN/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Жилийг сонгоно уу"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> сонгогдсон"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> устсан"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Ажлын <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Апп-дотор-түгжих-г ашиглах уу?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Апп-дотор-түгжих нь дэлгэцийг нэг апп дотор түгжинэ.\n\nГарахын тулд саяхны апп-ууд товчийг дараад барина уу $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"ҮГҮЙ"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"ЭХЛҮҮЛЭХ"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Апп-дотор-түгжих-г эхлүүлэх"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Апп-дотор-түгжих-с гарах"</string>
</resources>
diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml
index 49347a7..7c35ec5 100644
--- a/core/res/res/values-ms-rMY/strings.xml
+++ b/core/res/res/values-ms-rMY/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"Membenarkan apl untuk menetapkan penggera dalam apl penggera jam yang dipasang. Sesetengah applikasi jam penggera tidak boleh melaksanakan ciri ini."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"tambah mel suara"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Membenarkan apl untuk menambahkan mesej pada peti masuk mel suara anda."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"baca semua mel suara"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Membenarkan apl membaca semua mel suara anda."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"ubah suai kebenaran geolokasi Penyemak Imbas"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Membenarkan apl untuk mengubah suai kebenaran geolokasi Penyemak Imbas. Apl hasad boleh menggunakannya untuk membenarkan menghantar maklumat lokasi kepada laman web sembarangan."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"sahkan pakej"</string>
@@ -1727,4 +1725,18 @@
<string name="select_year" msgid="7952052866994196170">"Pilih tahun"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> dipilih"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> dipadamkan"</string>
+ <!-- no translation found for managed_profile_label_badge (2355652472854327647) -->
+ <skip />
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 40764c7..fb3f90e 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"Lar appen stille inn alarmen for en installert alarmklokke-app. Enkelte alarmklokke-apper implementerer kanskje ikke denne funksjonen."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"legge til talepost"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Lar appen legge til meldinger i talepostkassen din."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"lese alle meldingene i talepostkassen"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Lar appen lese alle meldingene i talepostkassen din."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"endre nettleserens tillatelser for geoposisjonering"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Lar appen endre nettleserens tillatelser for geoposisjonering. Ondsinnede apper kan bruke dette for å tillate sending av posisjonsinformasjon til vilkårlige nettsteder."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"bekrefte pakker"</string>
@@ -1727,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Velg året"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> er valgt"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> er slettet"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Jobb-<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Vil du bruke lås-til-app?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Lås-til-app låser skjermen i en enkelt app.\n\nFor å avslutte trykker du på og holder nede nylige apper-knappen $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NEI"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Start lås-til-app"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Avslutt lås-til-app"</string>
</resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 37c2594..b60d8c0 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Jaar selecteren"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> geselecteerd"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> verwijderd"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Werk <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Lock-to-app gebruiken?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Met Lock-to-app wordt het scherm vergrendeld in één app.\n\nAls u dit wilt afsluiten, houdt u de knop \'Recente apps\' $ ingedrukt"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NEE"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Lock-to-app starten"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Lock-to-app afsluiten"</string>
</resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index a3282e9..f71c8a8 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Wybierz rok"</string>
<string name="item_is_selected" msgid="949687401682476608">"Wybrałeś <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> usunięte"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (praca)"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Chcesz użyć funkcji lock-to-app?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Po włączeniu funkcji lock-to-app na ekranie będzie wyświetlona pojedyncza aplikacja.\n\nAby wyłączyć tę funkcję, naciśnij i przytrzymaj przycisk ostatnio używanych aplikacji $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NIE"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"START"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Włącz funkcję lock-to-app"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Wyłącz funkcję lock-to-app"</string>
</resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index e85d902..4c5140f 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1725,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"Selecionar ano"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selecionado"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> eliminado"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de trabalho"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 59328f5..e677fb7 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"Permite que o aplicativo defina um alarme em um aplicativo despertador instalado. Alguns aplicativos despertador podem não implementar este recurso."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"adicionar correio de voz"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que o aplicativo adicione mensagens a sua caixa de entrada do correio de voz."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"ler todo o correio de voz"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permite que o aplicativo leia todos os seus correios de voz."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modifique as permissões de geolocalização de seu navegador"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que o aplicativo modifique as permissões de geolocalização do navegador. Aplicativos maliciosos podem usar isso para permitir o envio de informações locais para sites arbitrários."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verificar pacotes"</string>
@@ -1727,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"Selecione o ano"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selecionado"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> excluído"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Trabalho: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml
index 83ba3a6..880a05d 100644
--- a/core/res/res/values-rm/strings.xml
+++ b/core/res/res/values-rm/strings.xml
@@ -2879,4 +2879,18 @@
<skip />
<!-- no translation found for deleted_key (7659477886625566590) -->
<skip />
+ <!-- no translation found for managed_profile_label_badge (2355652472854327647) -->
+ <skip />
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 11236b5..b734b22 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"Permite aplicaţiei să seteze o alarmă într-o aplicaţie de ceas cu alarmă instalată. Este posibil ca unele aplicaţii de ceas cu alarmă să nu implementeze această funcţie."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"adăugare mesagerie vocală"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite aplicaţiei să adauge mesaje în Mesaje primite în mesageria vocală."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"acces la toate mesajele vocale"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Permite aplicației să acceseze toate mesajele vocale."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"modificare permisiuni pentru locaţia geografică a browserului"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite aplicaţiei să modifice permisiunile privind locaţia geografică a browserului. Aplicaţiile rău intenţionate pot utiliza această permisiune pentru a permite trimiterea informaţiilor privind locaţia către site-uri web arbitrare."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"verificare pachete"</string>
@@ -1727,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"Selectați anul"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> selectat"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> a fost șters"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> de serviciu"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index c5f4c13..f727364 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1725,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"Выберите год"</string>
<string name="item_is_selected" msgid="949687401682476608">"Выбран элемент <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"Цифра <xliff:g id="KEY">%1$s</xliff:g> удалена"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Рабочий <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index eb685ba..0d4ee13 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"Umožňuje aplikácii nastaviť budík v nainštalovanej aplikácii budík. Niektoré aplikácie budíka nemusia túto funkciu implementovať."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"pridať hlasovú schránku"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Umožní aplikácii pridávať správy do doručenej pošty hlasovej schránky."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"čítanie všetkých hlasových schránok"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Umožňuje aplikácii čítať všetky vaše hlasové schránky."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"zmeniť povolenia prehliadača poskytovať informácie o zemepisnej polohe"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Umožňuje aplikácii zmeniť povolenia prehliadača na poskytovanie údajov o zemepisnej polohe. Škodlivé aplikácie to môžu použiť na odosielanie informácií o polohe ľubovoľným webovým stránkam."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"overiť balíky"</string>
@@ -1727,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"Vyberte rok"</string>
<string name="item_is_selected" msgid="949687401682476608">"Bola vybratá položka <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"Číslo <xliff:g id="KEY">%1$s</xliff:g> bolo odstránené"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Práca – <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 47568d6..44c523c 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"Programu omogoča nastavitev alarma v nameščenem programu budilke. Nekateri programi budilke morda nimajo te funkcije."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"dodajanje odzivnika"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Programu omogoča dodajanje sporočil prejetim sporočilom odzivnika."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"branje vseh sporočil v odzivniku"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Aplikaciji omogoča branje vseh sporočil v odzivniku."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Spreminjanje dovoljenj za geolokacijo brskalnika"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Programu omogoča spreminjanje geolokacijskih dovoljenj v brskalniku. Zlonamerni programi lahko to izkoristijo za pošiljanje podatkov o lokaciji poljubnim spletnim mestom."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"preveri pakete"</string>
@@ -1727,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Izberite leto"</string>
<string name="item_is_selected" msgid="949687401682476608">"Izbrano: <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"Številka <xliff:g id="KEY">%1$s</xliff:g> je izbrisana"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> za delo"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Želite uporabiti zaklepanje v aplikaciji?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Zaklepanje v aplikaciji zaklene zaslon v eni aplikaciji.\n\nČe želite zapustiti ta način, pritisnite in pridržite gumb za nedavne aplikacije $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NE"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"ZAŽENI"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Zagon zaklepanja v aplikaciji"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Izhod iz zaklepanja v aplikaciji"</string>
</resources>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 4758c94..a07b7fa 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1725,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"Изаберите годину"</string>
<string name="item_is_selected" msgid="949687401682476608">"Изабрали сте <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"Избрисали сте <xliff:g id="KEY">%1$s</xliff:g>"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> на послу"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index af85340..4db7351 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Välj år"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> har markerats"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> har tagits bort"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> för arbetet"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Vill du använda Lås till app?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Med funktionen Lås till app låses skärmen i en enskild app.\n\nAvsluta genom att trycka länge på knappen för de senaste apparna $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"NEJ"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"STARTA"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Starta Lås till app"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Avsluta Lås till app"</string>
</resources>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 755b9d2..df51691 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Chagua mwaka"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> kimechaguliwa"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> kimefutwa"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Kazi <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Ungependa kutumia lazimisha kutumia programu?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Lazimisha kutumia programu huonyeshwa katika programu moja. \n\n Ili uondoke bonyeza na ushikilie kitufe cha programu za hivi majuzi $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"HAPANA"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"ANZA"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Anzisha Lazimisha kutumia programu"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Ondoka kwenye Lazimisha kutumia programu"</string>
</resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index b03221d..a0f0bb5 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"เลือกปี"</string>
<string name="item_is_selected" msgid="949687401682476608">"เลือก <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"ลบ <xliff:g id="KEY">%1$s</xliff:g> แล้ว"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g>ที่ทำงาน"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"ใช้การล็อกแอปไหม"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"การล็อกแอปจะล็อกการแสดงไว้ในแอปเดียว\n\nหากต้องการออก ให้กดปุ่มแอปล่าสุด $ ค้างไว้"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"ไม่"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"เริ่มต้น"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"เริ่มใช้การล็อกแอป"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"ออกจากการล็อกแอป"</string>
</resources>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index af88c3c..2b0eb8c 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"Pinapayagan ang app na magtakda ng alarm sa isang naka-install na app ng alarm clock. Maaaring hindi ipatupad ng ilang apps ng alarm clock ang tampok na ito."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"magdagdag ng voicemail"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Pinapayagan ang app na magdagdag ng mga mensahe sa iyong inbox ng voicemail."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"basahin ang lahat ng voicemail"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Pinapayagan ang app na basahin ang lahat ng iyong voicemail."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"baguhin ang mga pahintulot ng geolocation ng Browser"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Pinapayagan ang app na baguhin ang mga pahintulot sa geolocation ng Browser. Maaari itong gamitin ng nakakahamak na apps upang payagan ang pagpapadala ng impormasyon ng lokasyon sa mga hindi tukoy na web site."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"i-verify ang mga package"</string>
@@ -1727,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Pumili ng taon"</string>
<string name="item_is_selected" msgid="949687401682476608">"Napili ang <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"Tinanggal ang <xliff:g id="KEY">%1$s</xliff:g>"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> sa Trabaho"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Gagamitin ang lock-to-app?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Nila-lock ng Lock-to-app ang display sa iisang app.\n\nUpang lumabas pindutin nang matagal ang button ng mga kamakailang app $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"HINDI"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"SIMULAN"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Simulan ang Lock-to-app"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Lumabas sa Lock-to-app"</string>
</resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index ee8c464..6c3ec18 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"Uygulamaya, çalar saat uygulamasının alarmını ayarlama izni verir. Bazı çalar saat uygulamaları bu özelliği uygulayamayabilir."</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"sesli mesaj ekle"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"Uygulamaya, sesli mesaj gelen kutunuza mesaj ekleme izni verir."</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"tüm sesli mesajları oku"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"Uygulamanın tüm sesli mesajlarınızı okumasına izin verir."</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Tarayıcı\'nın coğrafi konum izinlerini değiştir"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Uygulamaya, Tarayıcı\'nın coğrafi konum izinlerini değiştirme izni verir. Kötü amaçlı uygulamalar keyfi web sitelerine konum bilgisi gönderilmesini sağlamak için bunu kullanabilirler."</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"paketleri doğrula"</string>
@@ -1727,4 +1725,17 @@
<string name="select_year" msgid="7952052866994196170">"Yılı seçin"</string>
<string name="item_is_selected" msgid="949687401682476608">"<xliff:g id="ITEM">%1$s</xliff:g> seçildi"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> silindi"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> (İş)"</string>
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index b85fc99..f439595 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Виберіть рік"</string>
<string name="item_is_selected" msgid="949687401682476608">"Вибрано: <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> видалено"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Робоча <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Використовувати блокування в додатку?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Блокування в додатку блокує дисплей у певному додатку.\n\nЩоб вимкнути, натисніть і втримуйте кнопку останніх додатків ($)"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"НІ"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"УВІМКНУТИ"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Увімкнути блокування в додатку"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Вимкнути блокування в додатку"</string>
</resources>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index b38fb87..9d8905a 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1364,8 +1364,8 @@
<string name="permdesc_trust_listener" msgid="8233895334214716864">"Cho phép ứng dụng quan sát các thay đổi ở trạng thái đáng tin cậy."</string>
<string name="permlab_provide_trust_agent" msgid="5465587586091358316">"Cung cấp tác nhân đáng tin cậy."</string>
<string name="permdesc_provide_trust_agent" msgid="3865702641053068148">"Cho phép ứng dụng cung cấp tác nhân đáng tin cậy."</string>
- <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Khởi chạy trình đơn cài đặt đại lý đáng tin cậy."</string>
- <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Cho phép ứng dụng khởi chạy hoạt động thay đổi hoạt động của đại lý đáng tin cậy."</string>
+ <string name="permlab_launch_trust_agent_settings" msgid="7494179366945389098">"Chạy menu cài đặt đại lý đáng tin cậy."</string>
+ <string name="permdesc_launch_trust_agent_settings" msgid="985453787420853278">"Cho phép ứng dụng chạy hoạt động thay đổi hoạt động của đại lý đáng tin cậy."</string>
<string name="permlab_bind_trust_agent_service" msgid="8242093169457695334">"Liên kết với một dịch vụ của đại lý đáng tin cậy"</string>
<string name="permdesc_bind_trust_agent_service" msgid="7041930026024507515">"Cho phép ứng dụng liên kết với một dịch vụ của đại lý đáng tin cậy."</string>
<string name="permlab_recovery" msgid="3157024487744125846">"Tương tác với hệ thống khôi phục và bản cập nhật"</string>
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Chọn năm"</string>
<string name="item_is_selected" msgid="949687401682476608">"Đã chọn <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"Đã xóa <xliff:g id="KEY">%1$s</xliff:g>"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"<xliff:g id="LABEL">%1$s</xliff:g> làm việc"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Sử dụng lock-to-app?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app sẽ khóa màn hình trong một ứng dụng.\n\nĐể thoát, nhấn và giữ nút ứng dụng gần đây $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"KHÔNG"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"BẮT ĐẦU"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Bắt đầu Lock-to-app"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Thoát Lock-to-app"</string>
</resources>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index d730c49..eb1cb3b 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"允许应用在已安装的闹钟应用中设置闹钟。有些闹钟应用可能无法实现此功能。"</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"添加语音邮件"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"允许应用向您的语音信箱收件箱添加邮件。"</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"读取所有语音邮件"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"允许应用读取您所有的语音邮件。"</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"修改“浏览器”地理位置的权限"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"允许应用修改“浏览器”的地理位置权限。恶意应用可能借此向任意网站发送位置信息。"</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"验证软件包"</string>
@@ -1727,4 +1725,18 @@
<string name="select_year" msgid="7952052866994196170">"选择年份"</string>
<string name="item_is_selected" msgid="949687401682476608">"已选择<xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"已删除<xliff:g id="KEY">%1$s</xliff:g>"</string>
+ <!-- no translation found for managed_profile_label_badge (2355652472854327647) -->
+ <skip />
+ <!-- no translation found for lock_to_app_title (5895142291937470019) -->
+ <skip />
+ <!-- no translation found for lock_to_app_description (8597199033462406175) -->
+ <skip />
+ <!-- no translation found for lock_to_app_negative (8522854387366288195) -->
+ <skip />
+ <!-- no translation found for lock_to_app_positive (7085139175671313864) -->
+ <skip />
+ <!-- no translation found for lock_to_app_start (8889002974248178076) -->
+ <skip />
+ <!-- no translation found for lock_to_app_exit (7033017307788432861) -->
+ <skip />
</resources>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 43a487b..2db1ba6 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"允許應用程式在安裝的鬧鐘應用程式中設定鬧鐘,某些鬧鐘應用程式可能沒有這項功能。"</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"新增留言"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"允許應用程式將訊息加到您的留言信箱收件箱。"</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"讀取所有語音留言"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"允許應用程式讀取您的所有語音留言。"</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"修改瀏覽器地理資訊的權限"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"允許應用程式修改瀏覽器的地理資訊權限。惡意應用程式可能會藉此允許將您的位置資訊任意傳送給某些網站。"</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"驗證套件"</string>
@@ -1727,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"選取年份"</string>
<string name="item_is_selected" msgid="949687401682476608">"已選取<xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"<xliff:g id="KEY">%1$s</xliff:g> 已刪除"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"公司<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"使用 Lock-to-app?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app 會鎖定螢幕,只顯示單一應用程式的畫面。\n\n如要結束,請按住最近使用的應用程式按鈕 $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"否"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"啟動"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"啟動 Lock-to-app"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"結束 Lock-to-app"</string>
</resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 6658689..d362084 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1001,10 +1001,8 @@
<string name="permdesc_setAlarm" msgid="316392039157473848">"允許應用程式在安裝的鬧鐘應用程式中設定鬧鐘,某些鬧鐘應用程式可能無法執行這項功能。"</string>
<string name="permlab_addVoicemail" msgid="5525660026090959044">"新增語音留言"</string>
<string name="permdesc_addVoicemail" msgid="6604508651428252437">"允許應用程式將訊息新增至您的語音信箱收件匣。"</string>
- <!-- no translation found for permlab_readAllVoicemail (5834057671176753416) -->
- <skip />
- <!-- no translation found for permdesc_readAllVoicemail (7429033637738774985) -->
- <skip />
+ <string name="permlab_readAllVoicemail" msgid="5834057671176753416">"讀取所有語音留言"</string>
+ <string name="permdesc_readAllVoicemail" msgid="7429033637738774985">"允許應用程式讀取您的所有語音留言。"</string>
<string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"修改瀏覽器地理資訊的權限"</string>
<string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"允許應用程式修改瀏覽器的地理位置權限。請注意,惡意應用程式可能利用此功能允許將您的位置資訊任意傳送給某些網站。"</string>
<string name="permlab_packageVerificationAgent" msgid="5568139100645829117">"驗證套件"</string>
@@ -1727,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"選取年份"</string>
<string name="item_is_selected" msgid="949687401682476608">"已選取 <xliff:g id="ITEM">%1$s</xliff:g>"</string>
<string name="deleted_key" msgid="7659477886625566590">"已刪除 <xliff:g id="KEY">%1$s</xliff:g>"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"公司<xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"使用 Lock-to-app?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Lock-to-app 會鎖定螢幕,只顯示單一應用程式的畫面。\n\n如要結束,請按住最近使用的應用程式按鈕 $"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"否"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"啟動"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"啟動 Lock-to-app"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"結束 Lock-to-app"</string>
</resources>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 820ea50..23d2966 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1725,4 +1725,11 @@
<string name="select_year" msgid="7952052866994196170">"Khetha unyaka"</string>
<string name="item_is_selected" msgid="949687401682476608">"I-<xliff:g id="ITEM">%1$s</xliff:g> ekhethiwe"</string>
<string name="deleted_key" msgid="7659477886625566590">"I-<xliff:g id="KEY">%1$s</xliff:g> isusiwe"</string>
+ <string name="managed_profile_label_badge" msgid="2355652472854327647">"Umsebenzi <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="lock_to_app_title" msgid="5895142291937470019">"Sebenzisa isikhiya sohlelo lokusebenza?"</string>
+ <string name="lock_to_app_description" msgid="8597199033462406175">"Isikhiya kuhlelo lokusebenza sikhiyela isibonisi kuhlelo lokusebenza olulodwa.\n\nUkuze uphume cindezela uphinde ubambe inkinobho yezinhlelo zokusebenza zakamuva engu-$"</string>
+ <string name="lock_to_app_negative" msgid="8522854387366288195">"CHA"</string>
+ <string name="lock_to_app_positive" msgid="7085139175671313864">"QALA"</string>
+ <string name="lock_to_app_start" msgid="8889002974248178076">"Qala Isikhiya sohlelo lokusebenza"</string>
+ <string name="lock_to_app_exit" msgid="7033017307788432861">"Phuma kusikhiya sohlelo lokusebenza"</string>
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index a8a4d7a..e7e750d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5338,6 +5338,8 @@
resource are available in addition to the specific attributes of Fade
described here. -->
<declare-styleable name="Fade">
+ <!-- Equivalent to <code>visibilityMode</code>, fadingMode works only
+ with the Fade transition. -->
<attr name="fadingMode">
<!-- Fade will only fade appearing items in. -->
<enum name="fade_in" value="1" />
@@ -5366,6 +5368,21 @@
</attr>
</declare-styleable>
+ <!-- Use with {@link android.transition.Visibility} transitions, such as
+ <code>slide</code>, <code>explode</code>, and <code>fade</code> to mark which
+ views are supported. -->
+ <declare-styleable name="VisibilityTransition">
+ <!-- Changes whether the transition supports appearing and/or disappearing Views.
+ Corresponds to {@link android.transition.Visibility#setMode(int)}. -->
+ <attr name="visibilityMode">
+ <!-- Only appearing Views will be supported. -->
+ <enum name="mode_in" value="1" />
+ <!-- Only disappearing Views will be supported. -->
+ <enum name="mode_out" value="2" />
+ <!-- Both appearing and disappearing views will be supported. -->
+ <enum name="mode_in_out" value="3" />
+ </attr>
+ </declare-styleable>
<!-- Use <code>target</code> as the root tag of the XML resource that
describes a {@link android.transition.Transition#addTarget(int)
targetId} of a transition. There can be one or more targets inside
@@ -5430,9 +5447,9 @@
greater than 0 or infinite. The default value is restart. -->
<attr name="repeatMode"/>
<!-- Value the animation starts from. -->
- <attr name="valueFrom" format="float|integer|color|dimension"/>
+ <attr name="valueFrom" format="float|integer|color|dimension|string"/>
<!-- Value the animation animates to. -->
- <attr name="valueTo" format="float|integer|color|dimension"/>
+ <attr name="valueTo" format="float|integer|color|dimension|string"/>
<!-- The type of valueFrom and valueTo. -->
<attr name="valueType">
<!-- valueFrom and valueTo are floats. This is the default value is valueType is
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d350ef2..708fe67 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -116,8 +116,10 @@
specified for -large and -xlarge configurations. -->
<dimen name="config_prefDialogWidth">320dp</dimen>
- <!-- Enables or disables fading edges when marquee is enabled in TextView. -->
- <bool name="config_ui_enableFadingMarquee">true</bool>
+ <!-- Enables or disables fading edges when marquee is enabled in TextView.
+ Off by default, since the framebuffer readback used to implement the
+ fading edges is prohibitively expensive on most GPUs. -->
+ <bool name="config_ui_enableFadingMarquee">false</bool>
<!-- Whether dialogs should close automatically when the user touches outside
of them. This should not normally be modified. -->
@@ -1184,7 +1186,7 @@
<bool name="config_dreamsActivatedOnDockByDefault">true</bool>
<!-- If supported and enabled, are dreams activated when asleep and charging? (by default) -->
<bool name="config_dreamsActivatedOnSleepByDefault">false</bool>
- <!-- ComponentName of the default dream (Settings.Secure.SCREENSAVER_COMPONENT) -->
+ <!-- ComponentName of the default dream (Settings.Secure.DEFAULT_SCREENSAVER_COMPONENT) -->
<string name="config_dreamsDefaultComponent">com.google.android.deskclock/com.android.deskclock.Screensaver</string>
<!-- Are we allowed to dream while not plugged in? -->
@@ -1205,8 +1207,7 @@
try to start this dream if possible. The dream should typically call startDozing()
to put the display into a low power state and allow the application processor
to be suspended. When the dream ends, the system will go to sleep as usual.
- Specify the component name (Settings.Secure.SCREENSAVER_COMPONENT) or an
- empty string if none.
+ Specify the component name or an empty string if none.
Note that doze dreams are not subject to the same start conditions as ordinary dreams.
Doze dreams will run whenever the power manager is in a dozing state. -->
@@ -1570,6 +1571,7 @@
-->
<string-array translatable="false" name="config_globalActionsList">
<item>power</item>
+ <item>lockdown</item>
<item>bugreport</item>
<item>users</item>
</string-array>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 727d286..3206457 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2215,6 +2215,7 @@
<public type="attr" name="actionModeShareDrawable" />
<public type="attr" name="actionModeFindDrawable" />
<public type="attr" name="actionModeWebSearchDrawable" />
+ <public type="attr" name="visibilityMode" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e017f53..db02279 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -443,6 +443,9 @@
<!-- label for item that launches settings in phone options dialog [CHAR LIMIT=15]-->
<string name="global_action_settings">Settings</string>
+ <!-- label for item that locks the phone and enforces that it can't be unlocked without entering a credential. [CHAR LIMIT=15] -->
+ <string name="global_action_lockdown">Lock now</string>
+
<!-- Text to use when the number in a notification info is too large
(greater than status_bar_notification_info_maxnum, defined in
values/config.xml) and must be truncated. May need to be localized
@@ -460,7 +463,7 @@
<string name="user_owner_label">Personal apps</string>
<!-- Label for a corporate profile in the intent forwarding app. -->
- <string name="managed_profile_label">Android Work</string>
+ <string name="managed_profile_label">Work</string>
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgrouplab_costMoney">Services that cost you money</string>
@@ -2137,6 +2140,9 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_accessDrmCertificates">Allows an application to provision and use DRM certficates. Should never be needed for normal apps.</string>
+ <string name="permlab_handoverStatus">Receive handover transfer broadcasts.</string>
+ <string name="permdesc_handoverStatus">Allows receiving handover transfer status information.</string>
+
<!-- Policy administration -->
<!-- Title of policy access to limiting the user's password choices -->
@@ -2963,6 +2969,14 @@
not implement this feature.</string>
<!-- Title of an application permission, listed so the user can choose whether
+ they want to allow the application to remove voicemails from the user's voicemail
+ inbox. [CHAR LIMIT=NONE] -->
+ <string name="permlab_removeVoicemail">remove voicemails</string>
+ <!-- Description of an application permission, listed so the user can choose whether
+ they want to allow the application to do this. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_removeVoicemail">Allows the app to remove messages from your voicemail inbox.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether
they want to allow the application to do this. [CHAR LIMIT=NONE] -->
<string name="permlab_addVoicemail">add voicemail</string>
<!-- Description of an application permission, listed so the user can choose whether
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3e82d08..7442459 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1425,6 +1425,7 @@
<java-symbol type="string" name="global_action_silent_mode_off_status" />
<java-symbol type="string" name="global_action_silent_mode_on_status" />
<java-symbol type="string" name="global_action_toggle_silent_mode" />
+ <java-symbol type="string" name="global_action_lockdown" />
<java-symbol type="string" name="invalidPuk" />
<java-symbol type="string" name="lockscreen_carrier_default" />
<java-symbol type="style" name="Animation.LockScreen" />
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index b52b3a0..5d9fc66 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -156,7 +156,7 @@
<item name="windowTitleStyle">@style/WindowTitle.Material</item>
<item name="windowTitleSize">@dimen/action_bar_default_height_material</item>
<item name="windowTitleBackgroundStyle">@style/WindowTitleBackground.Material</item>
- <item name="windowContentTransitions">false</item>
+ <item name="windowContentTransitions">true</item>
<item name="windowAnimationStyle">@style/Animation.Material.Activity</item>
<item name="windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
<item name="windowActionBar">true</item>
@@ -521,6 +521,7 @@
<item name="windowEnterTransition">@transition/fade</item>
<item name="windowSharedElementEnterTransition">@transition/move</item>
<item name="windowSharedElementExitTransition">@transition/move</item>
+ <item name="windowContentTransitions">true</item>
<!-- Dialog attributes -->
<item name="dialogTheme">@style/Theme.Material.Light.Dialog</item>
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index e649baa..4015b3d 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -38,6 +38,7 @@
private static LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32);
private static LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
+ private static LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
public void assertLinkPropertiesEqual(LinkProperties source, LinkProperties target) {
// Check implementation of equals(), element by element.
@@ -245,11 +246,15 @@
// Add a route with no interface to a LinkProperties with no interface. No errors.
LinkProperties lp = new LinkProperties();
RouteInfo r = new RouteInfo(prefix, address, null);
- lp.addRoute(r);
+ assertTrue(lp.addRoute(r));
assertEquals(1, lp.getRoutes().size());
assertAllRoutesHaveInterface(null, lp);
- // Add a route with an interface. Except an exception.
+ // Adding the same route twice has no effect.
+ assertFalse(lp.addRoute(r));
+ assertEquals(1, lp.getRoutes().size());
+
+ // Add a route with an interface. Expect an exception.
r = new RouteInfo(prefix, address, "wlan0");
try {
lp.addRoute(r);
@@ -267,6 +272,7 @@
} catch (IllegalArgumentException expected) {}
// If the interface name matches, the route is added.
+ r = new RouteInfo(prefix, null, "wlan0");
lp.setInterfaceName("wlan0");
lp.addRoute(r);
assertEquals(2, lp.getRoutes().size());
@@ -352,7 +358,7 @@
// No addresses.
assertFalse(lp.hasIPv4Address());
- assertFalse(lp.hasIPv6Address());
+ assertFalse(lp.hasGlobalIPv6Address());
// Addresses on stacked links don't count.
LinkProperties stacked = new LinkProperties();
@@ -361,12 +367,12 @@
stacked.addLinkAddress(LINKADDRV4);
stacked.addLinkAddress(LINKADDRV6);
assertTrue(stacked.hasIPv4Address());
- assertTrue(stacked.hasIPv6Address());
+ assertTrue(stacked.hasGlobalIPv6Address());
assertFalse(lp.hasIPv4Address());
- assertFalse(lp.hasIPv6Address());
+ assertFalse(lp.hasGlobalIPv6Address());
lp.removeStackedLink(stacked);
assertFalse(lp.hasIPv4Address());
- assertFalse(lp.hasIPv6Address());
+ assertFalse(lp.hasGlobalIPv6Address());
// Addresses on the base link.
// Check the return values of hasIPvXAddress and ensure the add/remove methods return true
@@ -375,19 +381,29 @@
assertTrue(lp.addLinkAddress(LINKADDRV6));
assertEquals(1, lp.getLinkAddresses().size());
assertFalse(lp.hasIPv4Address());
- assertTrue(lp.hasIPv6Address());
+ assertTrue(lp.hasGlobalIPv6Address());
assertTrue(lp.removeLinkAddress(LINKADDRV6));
assertEquals(0, lp.getLinkAddresses().size());
- assertTrue(lp.addLinkAddress(LINKADDRV4));
- assertEquals(1, lp.getLinkAddresses().size());
- assertTrue(lp.hasIPv4Address());
- assertFalse(lp.hasIPv6Address());
- assertTrue(lp.addLinkAddress(LINKADDRV6));
+ assertTrue(lp.addLinkAddress(LINKADDRV6LINKLOCAL));
+ assertEquals(1, lp.getLinkAddresses().size());
+ assertFalse(lp.hasGlobalIPv6Address());
+
+ assertTrue(lp.addLinkAddress(LINKADDRV4));
assertEquals(2, lp.getLinkAddresses().size());
assertTrue(lp.hasIPv4Address());
- assertTrue(lp.hasIPv6Address());
+ assertFalse(lp.hasGlobalIPv6Address());
+
+ assertTrue(lp.addLinkAddress(LINKADDRV6));
+ assertEquals(3, lp.getLinkAddresses().size());
+ assertTrue(lp.hasIPv4Address());
+ assertTrue(lp.hasGlobalIPv6Address());
+
+ assertTrue(lp.removeLinkAddress(LINKADDRV6LINKLOCAL));
+ assertEquals(2, lp.getLinkAddresses().size());
+ assertTrue(lp.hasIPv4Address());
+ assertTrue(lp.hasGlobalIPv6Address());
// Adding an address twice has no effect.
// Removing an address that's not present has no effect.
diff --git a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
index cac6b93..ff2c8f0 100644
--- a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
+++ b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
@@ -20,8 +20,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.net.NetworkScorerAppManager.NetworkScorerAppData;
import android.test.InstrumentationTestCase;
import com.google.android.collect.Lists;
@@ -63,11 +65,11 @@
setScorers(package1, package2, package3);
- Iterator<String> result =
+ Iterator<NetworkScorerAppData> result =
NetworkScorerAppManager.getAllValidScorers(mMockContext).iterator();
assertTrue(result.hasNext());
- assertEquals("package1", result.next());
+ assertEquals("package1", result.next().mPackageName);
assertFalse(result.hasNext());
}
@@ -93,6 +95,7 @@
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = new ActivityInfo();
resolveInfo.activityInfo.packageName = packageName;
+ resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
if (hasReceiverPermission) {
resolveInfo.activityInfo.permission = permission.BROADCAST_SCORE_NETWORKS;
}
diff --git a/core/tests/coretests/src/android/net/RouteInfoTest.java b/core/tests/coretests/src/android/net/RouteInfoTest.java
index af6a32b..dcacd11 100644
--- a/core/tests/coretests/src/android/net/RouteInfoTest.java
+++ b/core/tests/coretests/src/android/net/RouteInfoTest.java
@@ -150,38 +150,68 @@
assertAreNotEqual(r1, r3);
}
- public void testHostRoute() {
+ public void testHostAndDefaultRoutes() {
RouteInfo r;
r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0");
assertFalse(r.isHostRoute());
+ assertTrue(r.isDefaultRoute());
+ assertTrue(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0");
assertFalse(r.isHostRoute());
+ assertTrue(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertTrue(r.isIPv6Default());
r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
assertFalse(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0");
assertFalse(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0");
assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0");
assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0");
assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0");
assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0");
assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
}
public RouteInfo passThroughParcel(RouteInfo r) {
diff --git a/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java b/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java
index d850c7c..9252270 100644
--- a/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java
+++ b/core/tests/inputmethodtests/src/android/os/CursorAnchorInfoTest.java
@@ -20,6 +20,7 @@
import android.graphics.RectF;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.CursorAnchorInfo.Builder;
@@ -81,7 +82,7 @@
assertEquals(SELECTION_START, info.getSelectionStart());
assertEquals(SELECTION_END, info.getSelectionEnd());
assertEquals(COMPOSING_TEXT_START, info.getComposingTextStart());
- assertEquals(COMPOSING_TEXT, info.getComposingText());
+ assertTrue(TextUtils.equals(COMPOSING_TEXT, info.getComposingText()));
assertEquals(INSERTION_MARKER_HORIZONTAL, info.getInsertionMarkerHorizontal());
assertEquals(INSERTION_MARKER_TOP, info.getInsertionMarkerTop());
assertEquals(INSERTION_MARKER_BASELINE, info.getInsertionMarkerBaseline());
@@ -97,7 +98,7 @@
assertEquals(SELECTION_START, info2.getSelectionStart());
assertEquals(SELECTION_END, info2.getSelectionEnd());
assertEquals(COMPOSING_TEXT_START, info2.getComposingTextStart());
- assertEquals(COMPOSING_TEXT, info2.getComposingText());
+ assertTrue(TextUtils.equals(COMPOSING_TEXT, info2.getComposingText()));
assertEquals(INSERTION_MARKER_HORIZONTAL, info2.getInsertionMarkerHorizontal());
assertEquals(INSERTION_MARKER_TOP, info2.getInsertionMarkerTop());
assertEquals(INSERTION_MARKER_BASELINE, info2.getInsertionMarkerBaseline());
@@ -110,12 +111,12 @@
assertEquals(info, info2);
assertEquals(info.hashCode(), info2.hashCode());
- // Make sure that object can be marshalled via {@link Parsel}.
+ // Make sure that object can be marshaled via {@link Parsel}.
final CursorAnchorInfo info3 = cloneViaParcel(info2);
assertEquals(SELECTION_START, info3.getSelectionStart());
assertEquals(SELECTION_END, info3.getSelectionEnd());
assertEquals(COMPOSING_TEXT_START, info3.getComposingTextStart());
- assertEquals(COMPOSING_TEXT, info3.getComposingText());
+ assertTrue(TextUtils.equals(COMPOSING_TEXT, info3.getComposingText()));
assertEquals(INSERTION_MARKER_HORIZONTAL, info3.getInsertionMarkerHorizontal());
assertEquals(INSERTION_MARKER_TOP, info3.getInsertionMarkerTop());
assertEquals(INSERTION_MARKER_BASELINE, info3.getInsertionMarkerBaseline());
diff --git a/docs/html/auto/images/assets/02_a_notif.gif b/docs/html/auto/images/assets/02_a_notif.gif
index 6a830ae..9f3d35d 100644
--- a/docs/html/auto/images/assets/02_a_notif.gif
+++ b/docs/html/auto/images/assets/02_a_notif.gif
Binary files differ
diff --git a/docs/html/auto/images/assets/02_b_switcher.gif b/docs/html/auto/images/assets/02_b_switcher.gif
index 626f94b..c4014e3 100644
--- a/docs/html/auto/images/assets/02_b_switcher.gif
+++ b/docs/html/auto/images/assets/02_b_switcher.gif
Binary files differ
diff --git a/docs/html/auto/images/assets/03_b_voice.gif b/docs/html/auto/images/assets/03_b_voice.gif
index ddd13e4..b280a9b 100644
--- a/docs/html/auto/images/assets/03_b_voice.gif
+++ b/docs/html/auto/images/assets/03_b_voice.gif
Binary files differ
diff --git a/docs/html/auto/images/assets/do_02_music_night.png b/docs/html/auto/images/assets/do_02_music_night.png
new file mode 100644
index 0000000..7faccfb
--- /dev/null
+++ b/docs/html/auto/images/assets/do_02_music_night.png
Binary files differ
diff --git a/docs/html/auto/images/assets/do_05_template.png b/docs/html/auto/images/assets/do_05_template.png
index 4e11243..63a07d8 100644
--- a/docs/html/auto/images/assets/do_05_template.png
+++ b/docs/html/auto/images/assets/do_05_template.png
Binary files differ
diff --git a/docs/html/auto/images/assets/do_05_template_night.png b/docs/html/auto/images/assets/do_05_template_night.png
new file mode 100644
index 0000000..6acd259
--- /dev/null
+++ b/docs/html/auto/images/assets/do_05_template_night.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/abarth.png b/docs/html/auto/images/logos/auto/abarth.png
new file mode 100644
index 0000000..fd1d228
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/abarth.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/alpharomeo.png b/docs/html/auto/images/logos/auto/alpharomeo.png
new file mode 100644
index 0000000..51c8c58
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/alpharomeo.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/audi.png b/docs/html/auto/images/logos/auto/audi.png
index 9b99efe..86cb06b 100644
--- a/docs/html/auto/images/logos/auto/audi.png
+++ b/docs/html/auto/images/logos/auto/audi.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/chrysler.png b/docs/html/auto/images/logos/auto/chrysler.png
index f366cba..c670c19 100644
--- a/docs/html/auto/images/logos/auto/chrysler.png
+++ b/docs/html/auto/images/logos/auto/chrysler.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/dodge.png b/docs/html/auto/images/logos/auto/dodge.png
new file mode 100644
index 0000000..4d6fcc0
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/dodge.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/fiat.png b/docs/html/auto/images/logos/auto/fiat.png
index aaa5cf3..b9783d9 100644
--- a/docs/html/auto/images/logos/auto/fiat.png
+++ b/docs/html/auto/images/logos/auto/fiat.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/ford.png b/docs/html/auto/images/logos/auto/ford.png
new file mode 100644
index 0000000..86a2670
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/ford.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/infinity.png b/docs/html/auto/images/logos/auto/infinity.png
index ad77351..a20e0a0 100644
--- a/docs/html/auto/images/logos/auto/infinity.png
+++ b/docs/html/auto/images/logos/auto/infinity.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/jeep.png b/docs/html/auto/images/logos/auto/jeep.png
new file mode 100644
index 0000000..024c3d1
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/jeep.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/mercedesbenz.png b/docs/html/auto/images/logos/auto/mercedesbenz.png
deleted file mode 100644
index 05fab0f..0000000
--- a/docs/html/auto/images/logos/auto/mercedesbenz.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/ram.png b/docs/html/auto/images/logos/auto/ram.png
new file mode 100644
index 0000000..9702587
--- /dev/null
+++ b/docs/html/auto/images/logos/auto/ram.png
Binary files differ
diff --git a/docs/html/auto/images/logos/auto/renault.png b/docs/html/auto/images/logos/auto/renault.png
index 65a4ae3..d252aa3 100644
--- a/docs/html/auto/images/logos/auto/renault.png
+++ b/docs/html/auto/images/logos/auto/renault.png
Binary files differ
diff --git a/docs/html/auto/index.jd b/docs/html/auto/index.jd
index 09374ad..63ac287 100644
--- a/docs/html/auto/index.jd
+++ b/docs/html/auto/index.jd
@@ -11,15 +11,6 @@
#jd-content>.content-footer.wrap {
display:none;
}
-</style>
-
-<style>
-#footer {
- display: none;
-}
-.content-footer {
- display: none;
-}
#hero-height {
height:calc(100% - 100px);
}
@@ -44,60 +35,32 @@
.auto-img-frame-cols {
z-index:2;
position:relative;
+ width:460px;
}
.auto-img-shot-cols {
+ width:447px;
position:absolute;
- top:5px;
- left:2px;
+ top:8px;
+ left:7px;
z-index:1;
}
-</style>
-
-
- <style>
-.fullpage>#footer,
-#jd-content>.content-footer.wrap {
- display:none;
+.sdk-link {
+ font-size:16px;
+ padding-left:5px;
+ padding-right:15px;
}
-</style>
-
-<style>
-#footer {
- display: none;
+.sdk-link-title {
+ font-size:18px;
+ padding-left:5px;
+ padding-right:15px;
}
-.content-footer {
- display: none;
+.sdk-link-desc {
+ font-size:14px;
+ padding-left:5px;
+ padding-right:15px;
}
-#hero-height {
- height:calc(100% - 110px);
-}
-.img-logo {
- margin:0 auto;
- display:block;
- margin-bottom:-28px !important;
-}
-.img-logo-thin {
- margin:0 auto;
- display:block;
- margin-bottom:-55px !important;
-}
-.col-5 {
- width:170px;
-}
-.auto-img-container-cols {
- position:relative;
- margin-bottom:25px;
- margin-top:25px;
-}
-.auto-img-frame-cols {
- z-index:2;
- position:relative;
-}
-.auto-img-shot-cols {
- position:absolute;
- top:5px;
- left:2px;
- z-index:1;
+.cols-leftp {
+ padding-left:95px;
}
</style>
@@ -116,9 +79,9 @@
<div class="landing-h1 hero">Android Auto</div>
<div class="landing-subhead hero">Entertainment and services on your dashboard</div>
<div class="landing-hero-description">
- <p style="width:450px">Display and control your handheld app in vehicles.
- Build apps with easy-to-use UI templates that
- let users keep their eyes on the road.</p>
+ <p style="width:450px">Display and control your Android app in vehicles.
+ Integrate your content with easy-to-use APIs and let Android Auto take
+ care of the rest.</p>
</div>
<div class="landing-body">
@@ -214,8 +177,8 @@
<div class="landing-section-header">
<div class="landing-h1">Minimize Distraction</div>
<div class="landing-subhead">
- Standard UI templates for several app categories let users focus on the road.
- </div>
+ Android Auto displays different UIs for several app categories that let users focus on the road.
+ </div>
</div>
<div class="landing-body">
<div class="cols">
@@ -232,11 +195,9 @@
</div>
</div>
</div>
- <p>These templates define the user interaction model for all
- apps and let you hook into a standard UI with touch and voice controls. The templates
- meet international best practices for reducing driver
- distraction while still letting you customize and brand them to properly deliver your
- content.
+ <p>Android Auto defines the user interaction model for all
+ apps and let you hook into a standard UI with touch and voice controls. The interface
+ is designed to reduce driver distraction while still letting you customize and brand them to properly deliver your content.
</p>
</div>
</div> <!-- end .wrap -->
@@ -247,29 +208,37 @@
<div class="landing-section-header">
<div class="landing-pre-h1">Coming soon</div>
<div class="landing-h1">Android Auto SDK</div>
- <div style="text-align:center;margin-top:20px;font-size:14pt;margin-bottom:-5px">
- <a href="https://docs.google.com/a/google.com/forms/d/1ANgYOoYLkfyZ2JRPSU34Nep5yNaU-Ha2syXJ9b4xLrA/viewform">Sign up for updates</a>
- </div>
</div>
<div class="landing-body">
<p>In the coming months, we’ll be releasing the Android Auto SDK, which includes APIs
and tools to make your existing apps compatible with Android Auto. The first version
- of the SDK will provide templates and APIs for music, podcast, live radio, and audio
+ of the SDK will provide APIs for music, podcast, live radio, and audio
news apps, as well as limited voice actions.</p>
- <p style="margin-bottom:40px">
- Future versions of the Android Auto SDK will include support for a selection of
- Android notifications, additional voice actions, and templates and APIs for
- messaging, communication, local search, and more.</p>
- <a target="_blank" href="http://g.co/androidautodev">
- <img class="landing-social-image" src="//www.google.com/images/icons/product/gplus-128.png" alt="+Android Auto Developers">
- </a>
- <p>
- Join the Android Auto developer community on Google+ to stay involved, get the
- latest updates, and exchange experiences with other developers.
- <a target="_blank" href="http://g.co/androidautodev">+Android Auto Developers</a>
- </p>
+ <div class="cols" style="margin-top:40px">
+ <div class="col-3-wide">
+ <p class="sdk-link-title">Updates</p>
+ <p class="sdk-link-desc">
+ Register to receive more information and be notified when the SDK is available.
+ </p>
+ <a class="sdk-link" href="https://docs.google.com/a/google.com/forms/d/1ANgYOoYLkfyZ2JRPSU34Nep5yNaU-Ha2syXJ9b4xLrA/viewform">Sign up for updates</a>
+ </div>
+ <div class="col-3-wide">
+ <p class="sdk-link-title">Google+ Community</p>
+ <p class="sdk-link-desc">
+ Stay involved, get updates, and exchange experiences with other developers.
+ </p>
+ <a class="sdk-link" href="http://g.co/androidautodev">Discuss on Google+</a>
+ </div>
+ <div class="col-3-wide">
+ <p class="sdk-link-title">Developer Overview</p>
+ <p class="sdk-link-desc">
+ Learn more about developing apps for Android Auto when the SDK is available.
+ </p>
+ <a class="sdk-link" href="{@docRoot}auto/overview.html">Learn about the platform</a>
+ </div>
+ </div>
</div>
</div> <!-- end .wrap -->
@@ -323,16 +292,13 @@
</div>
</div>
<div class="cols" style="margin-top:40px">
- <div class="col-4">
+ <div class="col-4">
<img src="{@docRoot}auto/images/logos/apps/iheartradio.png"
width="160" height="160" class="img-logo" />
</div>
<div class="col-12">
<p><em>
"The Android Auto APIs provide an easy way to integrate the most important features and functionality of iHeartRadio’s robust music service into a safety-minded automotive infotainment solution. The process was seamless, utilizing a flexible construct that allowed us to quickly adapt our existing product without losing any of the core experience our listeners know and love."</em></p>
-
- <b>Ryan Goff<br>
- Director of Automotive Integrations for Clear Channel Media and Entertainment</b>
</div>
</div>
<div class="cols" style="margin-top:60px">
@@ -344,11 +310,11 @@
<div class="col-12"><p style="margin-top:20px"><em>
"Android Auto offers Spotify the exciting opportunity to easily enable safe access to millions of songs while driving. We were able to quickly develop for the platform using the new Android voice and media API extensions. As a result, Android users will soon be able to continue the Spotify experience in their cars, including being able to play any song, artist, album or playlist by voice.</em></p>
</div>
+ </div>
</div>
- </div>
+ </div> <!-- end .wrap -->
+ </div> <!-- end .landing-section -->
- </div> <!-- end .wrap -->
- </div> <!-- end .landing-section -->
@@ -362,7 +328,15 @@
Android Auto is coming soon to new cars from these manufacturers
</div>
</div>
- <div class="cols">
+ <div class="cols cols-leftp">
+ <div class="col-5">
+ <img src="{@docRoot}auto/images/logos/auto/abarth.png"
+ width="120" height="120" class="img-logo" />
+ </div>
+ <div class="col-5">
+ <img src="{@docRoot}auto/images/logos/auto/alpharomeo.png"
+ width="120" height="120" class="img-logo" />
+ </div>
<div class="col-5">
<img src="{@docRoot}auto/images/logos/auto/acura.png"
width="120" height="120" class="img-logo" />
@@ -371,6 +345,8 @@
<img src="{@docRoot}auto/images/logos/auto/audi.png"
width="120" height="120" class="img-logo" />
</div>
+ </div>
+ <div class="cols cols-leftp">
<div class="col-5">
<img src="{@docRoot}auto/images/logos/auto/bentley.png"
width="120" height="120" class="img-logo" />
@@ -383,14 +359,18 @@
<img src="{@docRoot}auto/images/logos/auto/chrysler.png"
width="120" height="120" class="img-logo" />
</div>
- </div>
- <div class="cols">
<div class="col-5">
- <img src="{@docRoot}auto/images/logos/auto/datsun.png"
+ <img src="{@docRoot}auto/images/logos/auto/dodge.png"
+ width="120" height="120" class="img-logo" />
+ </div>
+ </div>
+ <div class="cols cols-leftp">
+ <div class="col-5">
+ <img src="{@docRoot}auto/images/logos/auto/fiat.png"
width="120" height="120" class="img-logo" />
</div>
<div class="col-5">
- <img src="{@docRoot}auto/images/logos/auto/fiat.png"
+ <img src="{@docRoot}auto/images/logos/auto/ford.png"
width="120" height="120" class="img-logo" />
</div>
<div class="col-5">
@@ -401,12 +381,16 @@
<img src="{@docRoot}auto/images/logos/auto/hyundai.png"
width="120" height="120" class="img-logo" />
</div>
+ </div>
+ <div class="cols cols-leftp">
<div class="col-5">
<img src="{@docRoot}auto/images/logos/auto/infinity.png"
width="120" height="120" class="img-logo" />
</div>
- </div>
- <div class="cols">
+ <div class="col-5">
+ <img src="{@docRoot}auto/images/logos/auto/jeep.png"
+ width="120" height="120" class="img-logo" />
+ </div>
<div class="col-5">
<img src="{@docRoot}auto/images/logos/auto/kia.png"
width="120" height="120" class="img-logo" />
@@ -415,20 +399,16 @@
<img src="{@docRoot}auto/images/logos/auto/maserati.png"
width="120" height="120" class="img-logo" />
</div>
+ </div>
+ <div class="cols cols-leftp">
<div class="col-5">
<img src="{@docRoot}auto/images/logos/auto/mazda.png"
width="120" height="120" class="img-logo" />
</div>
<div class="col-5">
- <img src="{@docRoot}auto/images/logos/auto/mercedesbenz.png"
- width="120" height="120" class="img-logo" />
- </div>
- <div class="col-5">
<img src="{@docRoot}auto/images/logos/auto/mitsubishi.png"
width="120" height="120" class="img-logo" />
</div>
- </div>
- <div class="cols">
<div class="col-5">
<img src="{@docRoot}auto/images/logos/auto/nissan.png"
width="120" height="120" class="img-logo" />
@@ -437,6 +417,12 @@
<img src="{@docRoot}auto/images/logos/auto/opel.png"
width="120" height="120" class="img-logo" />
</div>
+ </div>
+ <div class="cols cols-leftp">
+ <div class="col-5">
+ <img src="{@docRoot}auto/images/logos/auto/ram.png"
+ width="120" height="120" class="img-logo" />
+ </div>
<div class="col-5">
<img src="{@docRoot}auto/images/logos/auto/renault.png"
width="120" height="120" class="img-logo" />
@@ -450,7 +436,7 @@
width="120" height="120" class="img-logo" />
</div>
</div>
- <div class="cols">
+ <div class="cols cols-leftp">
<div class="col-5">
<img src="{@docRoot}auto/images/logos/auto/subaru.png"
width="120" height="120" class="img-logo" />
@@ -467,111 +453,12 @@
<img src="{@docRoot}auto/images/logos/auto/volvo.png"
width="120" height="120" class="img-logo" />
</div>
- <div class="col-5">
- <!--<img src="/auto/images/logos/auto/skoda.png"
- width="120" height="120" class="img-logo" />-->
- </div>
</div>
-
- <div class="landing-body landing-align-center">
- <div class="landing-subhead" style="margin-top:80px">
- Android Auto is also coming soon to aftermarket systems from these manufacturers:
- </div>
- </div>
- <div class="cols">
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/alpine.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/clarion.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/cloudcar.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/delphi.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/freescale.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- </div>
- <div class="cols">
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/fujitsuten.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/harman.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/jvc.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/kenwood.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/lg.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- </div>
- <div class="cols">
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/panasonic.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/parrot.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/pioneer.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/renesas.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/symphonyteleca.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- </div>
- <div class="cols">
- <div class="col-5">
- <img src="{@docRoot}auto/images/logos/hard/texasinstruments.png"
- width="120" height="120" class="img-logo-thin" />
- </div>
- <div class="col-5">
- <!--<img src="/auto/images/logos/hard/.png"
- width="120" height="120" class="img-logo-thin" />-->
- </div>
- <div class="col-5">
- <!--<img src="/auto/images/logos/hard/.png"
- width="120" height="120" class="img-logo-thin" />-->
- </div>
- <div class="col-5">
- <!--<img src="/auto/images/logos/hard/.png"
- width="120" height="120" class="img-logo-thin" />-->
- </div>
- <div class="col-5">
- <!--<img src="/auto/images/logos/hard/.png"
- width="120" height="120" class="img-logo-thin" />-->
- </div>
- </div>
- </div> <!-- end .wrap -->
- </div> <!-- end .landing-section -->
+ </div>
+ </div>
</div> <!-- end .landing-rest-of-page -->
-
-
- <div class="content-footer wrap" itemscope="" itemtype="http://schema.org/SiteNavigationElement">
+ <div class="content-footer wrap" itemscope="" itemtype="http://schema.org/SiteNavigationElement"
+ style="border-top: none;">
<div class="layout-content-col col-16" style="padding-top:4px">
<style>#___plusone_0 {float:right !important;}</style>
<div class="g-plusone" data-size="medium"></div>
@@ -586,9 +473,7 @@
License</a>.
</div>
</div>
-
-
- </div> <!-- end landing-body-content -->
+ </div> <!-- end .landing-hero-container -->
<script>
$("a.landing-down-arrow").on("click", function(e) {
@@ -598,3 +483,5 @@
e.preventDefault();
});
</script>
+
+</div> <!-- end landing-body-content -->
diff --git a/docs/html/auto/overview.jd b/docs/html/auto/overview.jd
index 728dff7..cf63b98 100644
--- a/docs/html/auto/overview.jd
+++ b/docs/html/auto/overview.jd
@@ -31,18 +31,16 @@
margin-top:10px;
}
.auto-img-frame-cols {
+ width:380px;
z-index:2;
position:relative;
}
.auto-img-shot-cols {
+ width:369px;
position:absolute;
- top:0px;
- left:0px;
+ top:7px;
+ left:6px;
z-index:1;
- border:1px solid #;
- -webkit-border-radius: 7px;
- -moz-border-radius: 7px;
- border-radius: 7px;
}
.auto-col-2 {
width:380px;
@@ -71,13 +69,13 @@
</ol>
</li>
<li><a href="#architecture">Architecture</a></li>
- <li><a href="#uitemplates">UI Templates</a>
+ <li><a href="#ui">User Interface</a>
<ol>
<li><a href="#launchapp">Launch App</a></li>
<li><a href="#useractions">User Actions</a></li>
<li><a href="#drawertransitions">Drawer Transitions</a></li>
<li><a href="#daynighttransitions">Day and Night Transitions</a></li>
- <li><a href="#customizetemplates">Customizing Templates</a></li>
+ <li><a href="#customizeui">Customizing the UI</a></li>
</ol>
</li>
<li><a href="#devprocess">Development Process</a></li>
@@ -89,27 +87,33 @@
<h1>Android Auto Developer Overview</h1>
-<p>Android Auto extends the Android platform to car entertainment systems. When users connect
-their Android handheld device to a compatible vehicle, Android Auto lets users project apps on
-the vehicle’s touchscreen and interact with them using the vehicle’s controls.</p>
+<p>Android Auto extends the Android platform into the car. When users connect
+their Android handheld device to a compatible vehicle, Android Auto provides a car-optimized
+Android experience on the vehicle's screen. Users interact with compatible apps and services
+through voice actions and the vehicle's input controls.</p>
+
+<p>The Android Auto SDK lets you easily extend your existing apps to work in the car, without
+having to worry about vehicle-specific hardware differences. You can use many Android APIs and
+services you are already familiar with. Android Auto provides an easy to use UI model and
+supports notifications and voice actions:</p>
<dl>
-<dt style="margin-bottom:10px"><strong>UI Templates</strong></dt>
+<dt style="margin-bottom:10px"><strong>Media UI</strong></dt>
<dd style="margin-bottom:20px">
-Android Auto defines interaction models and UI templates for several app categories. The
+Android Auto defines interaction models and car-specific UI patterns for apps. The
first version of Android Auto supports media apps, such as music, podcast, live radio, and
-audio news apps. Future versions will support messaging, communication, local search apps,
-and more.
+audio news apps.
</dd>
<dt style="margin-bottom:10px"><strong>Notifications</strong></dt>
<dd style="margin-bottom:20px">
The platform will integrate with existing Android APIs for notifications. Users will get
-some notifications from Android apps on the vehicle’s screen.</dd>
+car appropiate notifications from Android apps on the vehicle's screen.</dd>
<dt style="margin-bottom:10px"><strong>Voice Actions</strong></dt>
<dd style="margin-bottom:20px">
-Android Auto supports voice search and voice actions for media apps. Future versions
-will support additional voice actions.</dd>
+Android Auto supports a set of voice actions to interact with compatible apps and services.
+Apps can respond to the voice actions they're interested in, such as playing a particular song
+or taking a note.</dd>
<dt style="margin-bottom:10px"><strong>Easy Development Workflow</strong></dt>
<dd style="margin-bottom:20px">
@@ -124,39 +128,52 @@
<h2 id="design">Design</h2>
-<p>Digital experiences for cars should complement and augment driving, not demand the driver's
-attention. Designing these experiences for cars is fundamentally different than in the case of
-phones and tablets. It requires rethinking how these experiences unfold.</p>
+<p>Android Auto extends users' digital ecosystem into their cars, allowing drivers to stay
+connected to their virtual worlds while staying focused on the road ahead.</p>
-<h3 id="designprinciples">Design Principles</h3>
+<p>Because driving is the primary activity in the car, any digital experiences should be designed
+to complement and augment that activity. They should never demand the user's attention.</p>
-<p><strong>Glanceable</strong>. We designed Android Auto to reduce UI complexity, optimize user
-interactions, and lower cognitive load. Effective apps show just enough information
-and only provide features that do not require excessive menu interaction and navigation.</p>
+<p>Designing for cars is fundamentally different than designing for phones or tablets, and
+requires rethinking how experiences unfold. Because attention is limited and not all tasks are
+possible in the car, effective apps leverage the entire set of devices that drivers have,
+leveraging the app experience on those devices, outside of the car, to set the stage for simple
+experiences while driving.</p>
+
+<p>Android Auto experiences are:</p>
+
+<p><strong>Glanceable and simple</strong>. Driving requires users' full attention. In-car software
+should not. Android Auto was designed to simplify not only the UI, but to optimize interactions
+and require less thinking, induce lower cognitive load, and ultimately, be safer. Effective apps
+provide just enough information in the minimum amount of time the user needs to glance at it and
+return their attention back to the road. Apps should also reduce the number of features to only
+those that are safe and drive-appropriate.</p>
<p><strong>Predictive, yet predictable</strong>. Android Auto leverages rich, contextual awareness
-to keep the driver informed about important situations. Timely help is combined with predictable
-functions. Effective apps use patterns for common tasks and show timely information only when
-relevant.</p>
+to keep the driver informed about important situations during the drive. Rich, timely help is
+combined with predictable functions. Effective apps make use of the patterns for common tasks and
+show timely information only when relevant.</p>
-<p><strong>Connected</strong>. Android Auto works with apps that drivers already use in other
-devices. Android Auto promotes a continuous app experience from phones and tablets to cars,
-providing access to user's existing settings, subscriptions, and digital libraries. Experiences
-that bring personal content and context from other devices are part of Android Auto.</p>
+<p><strong>Connected</strong>. By leveraging the user's personal ecosystem of apps and services,
+Android Auto promotes a continuous experience from phone to car to other devices. The user's
+music, destinations, and virtual ecosystem are always available to augment the drive. Experiences
+that leverage personal context and other devices are naturally part of Android Auto.</p>
-<p><strong>Integrated</strong>. Android Auto blends your apps with the vehicle's entertainment
-system, creating a truly integrated experience in every car. By using the vehicle's screen and
-controls, apps feel tailored to each car.</p>
+<p><strong>Naturally integrated</strong>. Android Auto blends the user's apps with the car,
+creating a truly integrated experience that leverages what is unique about each car. By using
+the screens, controls, and capabilities of the vehicle, Android Auto feels like an extension of
+the car.</p>
+
<h2 id="architecture">Architecture</h2>
-<p>The Android Auto app projects your app's customized UI on the vehicle's screen. To communicate
+<p>The Android Auto app shows your app's customized UI on the vehicle's screen. To communicate
with the Android Auto app, your media app implements a set of media interfaces.</p>
<div style="width:750px;margin:0 auto">
-<img src="{@docRoot}auto/images/figure01.png" alt="" id="figure1" />
+<img src="{@docRoot}auto/images/figure01.png" alt="" />
<p class="img-caption">
<strong>Figure 1</strong> - Architecture of Android Auto.
</p>
@@ -167,7 +184,7 @@
<p><strong>Media App</strong> - Runs a media service that exposes content through browsing and
playback APIs. The service provides content to the Android Auto app. This is your Android app.</p>
-<p><strong>Android Auto App</strong> - Creates a templated UI and handles user interactions.
+<p><strong>Android Auto App</strong> - Creates the UI and handles user interactions.
This app uses a media client to request content from the media service running in the media
app. The client requests data from the media service and monitors service states.</p>
@@ -186,21 +203,20 @@
</ul>
-<h2 id="uitemplates">UI Templates</h2>
+<h2 id="ui">User Interface</h2>
-<p>The Android Auto app uses a templated UI to display content and user interaction
-opportunities. Android Auto provides you with a set of standard UI templates that follow
-international guidelines for minimizing driving distraction. You do not have to test your
-app's UI for for driver distraction, which is a lengthy and expensive process involving
-multiple legislations across the globe and different standards for each vehicle OEM.</p>
+<p>The Android Auto app uses a car-specific UI model to display content and user interaction
+opportunities. Android Auto provides you with a standard UI designed to minimize driver
+distraction. You do not have to test a custom UI for for driver distraction, which is a
+lengthy and expensive process involving multiple legislations across the globe and different
+standards for each vehicle OEM.</p>
-<p>The UI templates define interfaces for browsing, searching, and listening to content from
-media apps. Although you cannot change the standard template format or layout, you can customize
-the template colors, action icons, background images, and more.</p>
+<p>The UI defines interfaces for browsing, searching, and listening to content from
+media apps. You can customize the UI colors, action icons, background images, and more.</p>
-<h3 id="launchapp">Launch App Template</h3>
+<h3 id="launchapp">Launcher</h3>
-<p>The Launcher template shows all the compatible media apps installed on the user’s
+<p>The launcher shows all the compatible media apps installed on the user’s
Android device and lets users select one of them from an scrollable list:</p>
<div class="auto-img-container-single">
@@ -209,17 +225,15 @@
<img class="auto-img-shot-cols" src="/auto/images/assets/do_01_switcher.png" />
</div>
<p class="img-caption" style="margin-top:0px">
- <strong>Figure 2.</strong> The Launcher template.
+ <strong>Figure 2.</strong> The launcher.
</p>
</div>
-<h3>Primary App Template</h3>
+<h3>Primary App UI</h3>
-<p>After the user selects a media app, the display shows the primary app template. Figure
-3 shows the elements of this template that you can customize:</p>
-
-<p>You can customize the primary app template to show your own icons, app name, and
-background images. Figure 4 shows an example of a customized template:</p>
+<p>After the user selects a media app, the display shows the primary app UI.
+You can customize this UI to show your own icons, app name, and
+background images. Figure 3 shows an example of a customized UI:</p>
<div class="cols">
<div class="auto-col-2">
@@ -236,16 +250,16 @@
</div>
</div>
<p class="img-caption">
- <strong>Figure 4.</strong> A customized template.
+ <strong>Figure 3.</strong> A customized UI.
</p>
<h3 id="useractions">User Actions</h3>
-<p>The primary app template supports four main actions on the action bar, four auxiliary actions
+<p>The primary app UI supports four main actions on the action bar, four auxiliary actions
on the overflow bar, and the <em>Return</em> action. You can use standard controls and customize
-the actions and icons, as shown in Figure 5.</p>
+the actions and icons, as shown in Figure 4.</p>
<div class="auto-img-container-single">
<div class="auto-img-container">
@@ -253,18 +267,13 @@
<img class="auto-img-shot-cols" src="/auto/images/assets/do_03_more.png" />
</div>
<p class="img-caption" style="margin-top:0px">
- <strong>Figure 5.</strong> Custom extra actions.
+ <strong>Figure 4.</strong> Custom extra actions.
</p>
</div>
<h3 id="drawertransitions">Drawer Transitions</h3>
-<p>For browse actions, the display shows the drawer transition and template:</p>
-
-<p>After the transition from the primary app template to the drawer template, the drawer
-appears on the center. The customized drawer template shows the media containers and
-media files provided by the media service in your app. You can also customize drawers
-with icons for list items.</p>
+<p>For browse actions, the display shows the drawer transition as shown in Figure 5.</p>
<div class="cols">
<div class="auto-col-2">
@@ -281,18 +290,42 @@
</div>
</div>
<p class="img-caption">
- <strong>Figure 4.</strong> Generic and customized drawer templates.
+ <strong>Figure 5.</strong> Generic and customized drawers.
</p>
+<p>After the transition from the primary app UI to the drawer UI, the drawer
+appears on the center. The customized drawer UI shows the media containers and
+media files provided by the media service in your app. You can also customize drawers
+with icons for list items.</p>
+
+
<h3 id="daynighttransitions">Day and Night Transitions</h3>
-<p>All the templates support different color schemes for day and night, as shown in
-Figure 8. The platform provides the state (day or night) and makes adjustments automatically.</p>
+<p>All the UIs support different color schemes for day and night.
+The platform provides the state (day or night) and makes adjustments automatically.</p>
-<h3 id="customizetemplates">Customizing Templates</h3>
+<div class="cols">
+<div class="auto-col-2">
+ <div class="auto-img-container-cols">
+ <img class="auto-img-frame-cols" src="{@docRoot}auto/images/assets/00_frame.png" />
+ <img class="auto-img-shot-cols" src="{@docRoot}auto/images/assets/do_02_music.png" />
+ </div>
+</div>
+<div class="auto-col-2">
+ <div class="auto-img-container-cols">
+ <img class="auto-img-frame-cols" src="{@docRoot}auto/images/assets/00_frame.png" />
+ <img class="auto-img-shot-cols" src="{@docRoot}auto/images/assets/do_02_music_night.png" />
+ </div>
+</div>
+</div>
+<p class="img-caption">
+ <strong>Figure 6.</strong> Day and night modes.
+</p>
-<p>To customize the templates, provide the following app-specific resources and actions
-to the Android Auto media client.</p>
+<h3 id="customizeui">Customizing UIs</h3>
+
+<p>To customize the UI, you provide the following app-specific resources and actions
+to the Android Auto media client:</p>
<ul>
<li><strong>Resources</strong> - App logo, app name, theme colors, and background images.</li>
@@ -300,7 +333,7 @@
<em>Favorite</em>, and <em>Bookmark</em>. These actions are app-specific.</li>
</ul>
-<p>If provided, the media client automatically uses them in the templated UI.</p>
+<p>If provided, the media client automatically uses them in the UI.</p>
<h2 id="devprocess">Development Process</h2>
@@ -312,10 +345,10 @@
<p>To create a media app for Android Auto, you include an Android service in your app
that implements the media service interfaces provided by the Android Auto SDK. These
interfaces define functionality for browsing and finding content, playing media,
-customizing the UI template, and performing app-specific actions.</p>
+customizing the UI, and performing app-specific actions.</p>
<p>The media service interfaces present the content library as a navigable tree and enable
-clients to play media, get album art, obtain theme resources for the UI template, and
+clients to play media, get album art, obtain theme resources for the UI, and
invoke app-specific actions.</p>
<p>You don’t have to create a new app for Android Auto: you can extend your existing
diff --git a/docs/html/design/auto/index.jd b/docs/html/design/auto/index.jd
new file mode 100644
index 0000000..c970cac
--- /dev/null
+++ b/docs/html/design/auto/index.jd
@@ -0,0 +1,35 @@
+page.title=Android Auto
+@jd:body
+
+<style>
+.auto-img-container-cols {
+ position:relative;
+ margin-bottom:25px;
+ margin-top:25px;
+}
+.auto-img-frame-cols {
+ z-index:2;
+ position:relative;
+}
+.auto-img-shot-cols {
+ position:absolute;
+ top:5px;
+ left:2px;
+ z-index:1;
+}
+</style>
+
+<div class="auto-img-container-cols" style="float:right; margin:0 0 40px 40px;width:460px">
+ <img class="auto-img-frame-cols" src="/auto/images/assets/00_frame.png">
+ <img class="auto-img-shot-cols" src="/auto/images/assets/03_a_musict.png">
+</div>
+
+<p>Android Auto is <strong>coming soon</strong> and brings apps to the car,
+integrating with the vehicle's input controls and display.</p>
+
+<p>The future design guidelines provide templates that define the user interaction model for all apps and let you hook into a standard UI with touch and voice controls. The templates meet international best practices for reducing driver distraction while still letting you customize and brand them to properly deliver your content.</p>
+
+<p><a href="{@docRoot}auto/index.html">Learn more about Android Auto</a>.</p>
+
+
+
diff --git a/docs/html/design/design_toc.cs b/docs/html/design/design_toc.cs
index 2bd0bf9..885f336 100644
--- a/docs/html/design/design_toc.cs
+++ b/docs/html/design/design_toc.cs
@@ -5,7 +5,26 @@
<ul>
<li><a href="<?cs var:toroot ?>design/get-started/creative-vision.html">Creative Vision</a></li>
<li><a href="<?cs var:toroot ?>design/get-started/principles.html">Design Principles</a></li>
- <li><a href="<?cs var:toroot ?>design/get-started/ui-overview.html">UI Overview</a></li>
+ </ul>
+ </li>
+
+ <li class="nav-section">
+ <div class="nav-section-header"><a href="<?cs var:toroot ?>design/devices.html">Devices</a></div>
+ <ul>
+ <li><a href="<?cs var:toroot ?>design/handhelds/index.html">Phones & Tablets</a></li>
+ <li class="nav-section">
+ <div class="nav-section-header">
+ <a href="<?cs var:toroot ?>design/wear/index.html">Wear</a></div>
+ <ul>
+ <li><a href="<?cs var:toroot ?>design/wear/creative-vision.html">Creative Vision</a></li>
+ <li><a href="<?cs var:toroot ?>design/wear/principles.html">Design Principles</a></li>
+ <li><a href="<?cs var:toroot ?>design/wear/structure.html">App Structure</a></li>
+ <li><a href="<?cs var:toroot ?>design/wear/patterns.html">UI Patterns</a></li>
+ <li><a href="<?cs var:toroot ?>design/wear/style.html">Style</a></li>
+ </ul>
+ </li>
+ <li><a href="<?cs var:toroot ?>design/tv/index.html">TV</a></li>
+ <li><a href="<?cs var:toroot ?>design/auto/index.html">Auto</a></li>
</ul>
</li>
@@ -67,25 +86,6 @@
</li>
<li class="nav-section">
- <div class="nav-section-header"><a href="<?cs var:toroot ?>design/devices.html">Devices</a></div>
- <ul>
- <!-- wear design goes here -->
- <li class="nav-section">
- <div class="nav-section-header">
- <a href="<?cs var:toroot ?>design/tv/index.html">TV</a></div>
- <ul>
- <li><a href="<?cs var:toroot ?>design/tv/principles.html">Design Principles</a></li>
- <li><a href="<?cs var:toroot ?>design/tv/ui-overview.html">UI Overview</a></li>
- <li><a href="<?cs var:toroot ?>design/tv/style.html">Style</a></li>
- <li><a href="<?cs var:toroot ?>design/tv/patterns.html">Patterns</a></li>
- </ul>
- </li>
-
- </ul>
- </li>
-
-
- <li class="nav-section">
<div class="nav-section-header empty"><a href="<?cs var:toroot ?>design/downloads/index.html">Downloads</a></div>
</li>
diff --git a/docs/html/design/devices.jd b/docs/html/design/devices.jd
new file mode 100644
index 0000000..c67e585
--- /dev/null
+++ b/docs/html/design/devices.jd
@@ -0,0 +1,37 @@
+page.title=Devices
+page.viewport_width=970
+section.landing=true
+header.hide=1
+footer.hide=1
+@jd:body
+
+<style>
+#landing-graphic-container {
+ position: relative;
+}
+
+#text-overlay {
+ position: absolute;
+ left: 0;
+ top: 410px;
+ width: 340px;
+
+}
+#hero-image {
+}
+</style>
+
+<div id="landing-graphic-container">
+ <div id="text-overlay">
+ <p itemprop="description">The device-centric UI principles, overviews, and detailed guidelines
+ described here build on the core <a href="{@docRoot}design/get-started/principles.html">Android Design Principles</a>
+ to provide more specific design guidance for different form factors.
+ </p>
+ <p>
+ <a href="{@docRoot}design/handhelds/index.html" class="landing-page-link">Phones & Tablets</a></p>
+ </div>
+ <a id="hero-image" href="{@docRoot}design/handhelds/index.html">
+ <img src="{@docRoot}design/media/device_family.png">
+ </a>
+</div>
+
diff --git a/docs/html/design/downloads/index.jd b/docs/html/design/downloads/index.jd
index ddeda5c..278617b 100644
--- a/docs/html/design/downloads/index.jd
+++ b/docs/html/design/downloads/index.jd
@@ -2,27 +2,12 @@
page.tags=Icons,stencils,color swatches
@jd:body
-<div class="layout-content-row">
- <div class="layout-content-col span-9">
-<p>Want everything? We've bundled all the downloads available on Android Design, except for the
- <a href="#roboto">Roboto</a> font family, into a single ZIP file. You can also download
- individual files listed below.</p>
+<p>You may use these materials without restriction to facilitate your app design
+and implementation.</p>
-<p>You may use these materials without restriction in your apps and to develop your apps.</p>
- </div>
- <div class="layout-content-col span-4">
-
-<p>
- <a class="download-button" onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'All Design Assets']);"
- href="{@docRoot}downloads/design/Android_Design_Downloads_20131106.zip">Download All</a>
-</p>
-
- </div>
-</div>
-
-<h2 id="stencils">Stencils and Sources</h2>
+<h2 id="stencils">Phone & Tablet Stencils</h2>
<div class="layout-content-row">
<div class="layout-content-col span-5">
@@ -54,6 +39,11 @@
</div>
</div>
+
+
+
+
+
<h2 id="action-bar-icon-pack">Action Bar Icon Pack</h2>
<div class="layout-content-row">
@@ -81,6 +71,76 @@
</div>
</div>
+
+
+
+
+<h2 id="Wear">Android Wear Materials</h2>
+
+<div class="layout-content-row">
+ <div class="layout-content-col span-5">
+ <h4>UI toolkit</h4>
+<p>The toolkit contains detailed specs and measurements of all of the primary Android Wear UI components. Available in PDF and Illustrator formats.</p>
+
+ </div>
+ <div class="layout-content-col span-4">
+
+ <img src="{@docRoot}design/media/downloads_wear_toolkit.png" width="220">
+
+ </div>
+ <div class="layout-content-col span-4">
+ <a class="download-button" onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Wear Toolkit AI']);"
+ href="{@docRoot}downloads/design/Android_Design_Stencils_Sources_20131106.zip">Adobe® Illustrator® Toolkit</a>
+
+ <a class="download-button" onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Wear Toolkit PDF']);"
+ href="{@docRoot}downloads/design/Android_Design_Stencils_Sources_20131106.zip">PDF Toolkit</a>
+ </div>
+</div>
+
+<div class="layout-content-row">
+ <div class="layout-content-col span-5">
+ <h4>Sample app user flows</h4>
+<p>Examples of how to chain together simple Android Wear UI components into common user flows, from simple notifications to complex interactions involving full screen activities.
+</p>
+
+ </div>
+ <div class="layout-content-col span-4">
+
+ <img src="{@docRoot}design/media/downloads_wear_flows.png" width="220">
+
+ </div>
+ <div class="layout-content-col span-4">
+ <a class="download-button" onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Wear Sample Flows AI']);"
+ href="{@docRoot}downloads/design/Android_Design_Stencils_Sources_20131106.zip">Adobe® Illustrator® App Flows</a>
+ <a class="download-button" onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Wear Sample Flows PDF']);"
+ href="{@docRoot}downloads/design/Android_Design_Stencils_Sources_20131106.zip">PDF App Flows</a>
+ </div>
+</div>
+
+<div class="layout-content-row">
+ <div class="layout-content-col span-5">
+ <h4>Sample app design mocks</h4>
+<p>Stream cards and UI elements for some example apps in fully editable PSD format.
+</p>
+
+ </div>
+ <div class="layout-content-col span-4">
+
+ <img src="{@docRoot}design/media/downloads_wear_psds.png" width="220">
+
+ </div>
+ <div class="layout-content-col span-4">
+ <a class="download-button" onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Wear Sample PSD Mocks']);"
+ href="{@docRoot}downloads/design/Android_Wear_Sample_Assets.zip">Adobe® Photoshop® mocks</a>
+ </div>
+</div>
+
+
+
+
+
+
+
<h2 id="style">Style</h2>
<div class="layout-content-row">
@@ -89,6 +149,7 @@
<h4 id="roboto">Roboto</h4>
<p>Ice Cream Sandwich introduced a new type family named Roboto, created specifically for the
requirements of UI and high-resolution screens.</p>
+<p>For Android Wear, Roboto Condensed is the system font and the Regular and Light variants should be used by all Wear apps.</p>
<p><a href="{@docRoot}design/style/typography.html">More on Roboto</a></p>
<p><a href="http://www.google.com/fonts/specimen/Roboto" class="external-link">Roboto on Google Fonts</a></p>
<p><a href="http://www.google.com/fonts/specimen/Roboto+Condensed" class="external-link">Roboto Condensed on Google Fonts</a></p>
diff --git a/docs/html/design/get-started/creative-vision.jd b/docs/html/design/get-started/creative-vision.jd
index 1ce305a..9261c6e 100644
--- a/docs/html/design/get-started/creative-vision.jd
+++ b/docs/html/design/get-started/creative-vision.jd
@@ -6,10 +6,10 @@
<div class="vspace size-1"> </div>
<p itemprop="description">
- We focused the design of Android around three overarching goals, which apply
- to our core apps as well as the system at large. As you design apps to work
- with Android, consider these goals: <em>Enchant me</em>, <em>Simplify my
- life</em>, and <em>Make me amazing</em>
+ Starting with Ice Cream Sandwich, we focused the design of
+ Android around these three overarching goals, which apply
+ to our core apps as well as the system at large.
+ As you work with Android, consider these goals.
</p>
<div class="vspace size-1"> </div>
diff --git a/docs/html/design/get-started/principles.jd b/docs/html/design/get-started/principles.jd
index 0b7147b..73ec3a6 100644
--- a/docs/html/design/get-started/principles.jd
+++ b/docs/html/design/get-started/principles.jd
@@ -1,9 +1,16 @@
-page.title=Design Principles
+page.title=Android Design Principles
@jd:body
-<p>These design principles were developed by and for the Android User Experience Team to keep users'
-best interests in mind. Consider them as you apply your own creativity and design thinking. Deviate
-with purpose.</p>
+<p>These design principles were developed by and for the Android
+User Experience Team to keep users' best interests in mind.
+For Android developers and designers, they continue to
+underlie the more detailed design guidelines for different
+types of devices.</p>
+
+<p>
+Consider these principles as you apply your own
+creativity and design thinking. Deviate with purpose.
+</p>
<h2 id="enchant-me">Enchant Me</h2>
diff --git a/docs/html/design/get-started/ui-overview.jd b/docs/html/design/handhelds/index.jd
similarity index 87%
rename from docs/html/design/get-started/ui-overview.jd
rename to docs/html/design/handhelds/index.jd
index 5f4c40f..882b070 100644
--- a/docs/html/design/get-started/ui-overview.jd
+++ b/docs/html/design/handhelds/index.jd
@@ -1,12 +1,20 @@
-page.title=UI Overview
+page.title=Phones & Tablets
@jd:body
-<p>Android's system UI provides the framework on top of which you build your app. Important aspects
-include the Home screen experience, global device navigation, and notifications.</p>
-<p>Your app will play an important part in keeping the overall Android experience consistent and
-enjoyable to use. At the end of this chapter we introduce the main elements for achieving this goal
-in your app.</p>
-<p>Read on for a quick overview of the most important aspects of the Android user interface.</p>
+<p>
+Android's system UI provides the framework on top of which you build your app,
+whether you're designing for phones, tablets, watches, or other form factors.
+Aspects of UI that are especially important for phones and tablets include
+the Home screen experience, global device navigation, and notifications.
+</p>
+
+<p>
+Your app will play an important part in keeping the overall Android experience
+consistent and enjoyable to use. This page introduces some of the main elements
+that can help you achieve this goal. The main Android Design topics listed on
+the left, after the Devices sections, provide detailed guidelines for phones
+and tablets.
+</p>
<h2 id="home-all-apps-recents">Home, All Apps, and Recents</h2>
diff --git a/docs/html/design/index.jd b/docs/html/design/index.jd
index cb7dd4f..27e3169 100644
--- a/docs/html/design/index.jd
+++ b/docs/html/design/index.jd
@@ -13,7 +13,7 @@
#text-overlay {
position: absolute;
- left: 36px;
+ left: 0;
top: 42px;
width: 266px;
@@ -34,5 +34,15 @@
<a id="hero-image" href="/design/get-started/creative-vision.html">
<img src="/design/media/index_landing_page.png">
</a>
+
+<div style="background: hsl(8, 70%, 54%); margin: 0; padding: 20px 20px 10px 20px;color: #fff; position: absolute;top: 255px;width: 179px;">
+<h2 style="color: #fff;margin:0 0 10px; font-size:18px" class="norule">L Developer Preview</h2>
+<p> The next version of Android uses a design
+metaphor inspired by paper and ink that provides a reassuring sense of tactility. Before it arrives for users, you can get an early
+look at the new Material design.
+</p>
+<p><a class="white" href="{@docRoot}preview/material/index.html">Learn more about Material</a></p>
+</div>
+
</div>
diff --git a/docs/html/design/media/android-tv.png b/docs/html/design/media/android-tv.png
new file mode 100644
index 0000000..98005cd
--- /dev/null
+++ b/docs/html/design/media/android-tv.png
Binary files differ
diff --git a/docs/html/design/media/device_family.png b/docs/html/design/media/device_family.png
new file mode 100644
index 0000000..96b31d3
--- /dev/null
+++ b/docs/html/design/media/device_family.png
Binary files differ
diff --git a/docs/html/design/media/downloads_wear_flows.png b/docs/html/design/media/downloads_wear_flows.png
new file mode 100644
index 0000000..5669a2d
--- /dev/null
+++ b/docs/html/design/media/downloads_wear_flows.png
Binary files differ
diff --git a/docs/html/design/media/downloads_wear_psds.png b/docs/html/design/media/downloads_wear_psds.png
new file mode 100644
index 0000000..2b071a3
--- /dev/null
+++ b/docs/html/design/media/downloads_wear_psds.png
Binary files differ
diff --git a/docs/html/design/media/downloads_wear_toolkit.png b/docs/html/design/media/downloads_wear_toolkit.png
new file mode 100644
index 0000000..c3d30e8
--- /dev/null
+++ b/docs/html/design/media/downloads_wear_toolkit.png
Binary files differ
diff --git a/docs/html/design/media/wear/1D_picker.png b/docs/html/design/media/wear/1D_picker.png
new file mode 100644
index 0000000..46c6bf6
--- /dev/null
+++ b/docs/html/design/media/wear/1D_picker.png
Binary files differ
diff --git a/docs/html/design/media/wear/2D_picker.png b/docs/html/design/media/wear/2D_picker.png
new file mode 100644
index 0000000..82c766a
--- /dev/null
+++ b/docs/html/design/media/wear/2D_picker.png
Binary files differ
diff --git a/docs/html/design/media/wear/2D_picker_action.png b/docs/html/design/media/wear/2D_picker_action.png
new file mode 100644
index 0000000..8560ef8
--- /dev/null
+++ b/docs/html/design/media/wear/2D_picker_action.png
Binary files differ
diff --git a/docs/html/design/media/wear/2d_picker_indicated.png b/docs/html/design/media/wear/2d_picker_indicated.png
new file mode 100644
index 0000000..d31c099
--- /dev/null
+++ b/docs/html/design/media/wear/2d_picker_indicated.png
Binary files differ
diff --git a/docs/html/design/media/wear/Bluebird.png b/docs/html/design/media/wear/Bluebird.png
new file mode 100644
index 0000000..7b911c7
--- /dev/null
+++ b/docs/html/design/media/wear/Bluebird.png
Binary files differ
diff --git a/docs/html/design/media/wear/Dismissing-cards.png b/docs/html/design/media/wear/Dismissing-cards.png
new file mode 100644
index 0000000..973ef39
--- /dev/null
+++ b/docs/html/design/media/wear/Dismissing-cards.png
Binary files differ
diff --git a/docs/html/design/media/wear/action_button.png b/docs/html/design/media/wear/action_button.png
new file mode 100644
index 0000000..dfdffa3
--- /dev/null
+++ b/docs/html/design/media/wear/action_button.png
Binary files differ
diff --git a/docs/html/design/media/wear/action_on_card.png b/docs/html/design/media/wear/action_on_card.png
new file mode 100644
index 0000000..d0b0fff
--- /dev/null
+++ b/docs/html/design/media/wear/action_on_card.png
Binary files differ
diff --git a/docs/html/design/media/wear/appstructuresample.png b/docs/html/design/media/wear/appstructuresample.png
new file mode 100644
index 0000000..0e5b86f
--- /dev/null
+++ b/docs/html/design/media/wear/appstructuresample.png
Binary files differ
diff --git a/docs/html/design/media/wear/assets_specifics.png b/docs/html/design/media/wear/assets_specifics.png
new file mode 100644
index 0000000..00f97d1
--- /dev/null
+++ b/docs/html/design/media/wear/assets_specifics.png
Binary files differ
diff --git a/docs/html/design/media/wear/biggesture.png b/docs/html/design/media/wear/biggesture.png
new file mode 100644
index 0000000..6c6f0cd
--- /dev/null
+++ b/docs/html/design/media/wear/biggesture.png
Binary files differ
diff --git a/docs/html/design/media/wear/bridgednotifications.jpg b/docs/html/design/media/wear/bridgednotifications.jpg
new file mode 100644
index 0000000..a9e57a4
--- /dev/null
+++ b/docs/html/design/media/wear/bridgednotifications.jpg
Binary files differ
diff --git a/docs/html/wear/images/circle_message2.png b/docs/html/design/media/wear/circle_message2.png
similarity index 100%
rename from docs/html/wear/images/circle_message2.png
rename to docs/html/design/media/wear/circle_message2.png
Binary files differ
diff --git a/docs/html/design/media/wear/clear_bold_type.jpg b/docs/html/design/media/wear/clear_bold_type.jpg
new file mode 100644
index 0000000..e4b742c
--- /dev/null
+++ b/docs/html/design/media/wear/clear_bold_type.jpg
Binary files differ
diff --git a/docs/html/design/media/wear/confirmation.png b/docs/html/design/media/wear/confirmation.png
new file mode 100644
index 0000000..513b85f
--- /dev/null
+++ b/docs/html/design/media/wear/confirmation.png
Binary files differ
diff --git a/docs/html/wear/images/fitness-24.png b/docs/html/design/media/wear/context_fitness.png
similarity index 100%
rename from docs/html/wear/images/fitness-24.png
rename to docs/html/design/media/wear/context_fitness.png
Binary files differ
diff --git a/docs/html/design/media/wear/context_lights.png b/docs/html/design/media/wear/context_lights.png
new file mode 100644
index 0000000..6374bdd
--- /dev/null
+++ b/docs/html/design/media/wear/context_lights.png
Binary files differ
diff --git a/docs/html/design/media/wear/context_running.png b/docs/html/design/media/wear/context_running.png
new file mode 100644
index 0000000..8b44e8c
--- /dev/null
+++ b/docs/html/design/media/wear/context_running.png
Binary files differ
diff --git a/docs/html/design/media/wear/context_workout.png b/docs/html/design/media/wear/context_workout.png
new file mode 100644
index 0000000..2ed8901
--- /dev/null
+++ b/docs/html/design/media/wear/context_workout.png
Binary files differ
diff --git a/docs/html/design/media/wear/continue_phone.png b/docs/html/design/media/wear/continue_phone.png
new file mode 100644
index 0000000..fed93b6
--- /dev/null
+++ b/docs/html/design/media/wear/continue_phone.png
Binary files differ
diff --git a/docs/html/design/media/wear/copywrite.png b/docs/html/design/media/wear/copywrite.png
new file mode 100644
index 0000000..78be0bd
--- /dev/null
+++ b/docs/html/design/media/wear/copywrite.png
Binary files differ
diff --git a/docs/html/design/media/wear/countdown.png b/docs/html/design/media/wear/countdown.png
new file mode 100644
index 0000000..11b1504
--- /dev/null
+++ b/docs/html/design/media/wear/countdown.png
Binary files differ
diff --git a/docs/html/design/media/wear/customlayout.jpg b/docs/html/design/media/wear/customlayout.jpg
new file mode 100644
index 0000000..9573cfc
--- /dev/null
+++ b/docs/html/design/media/wear/customlayout.jpg
Binary files differ
diff --git a/docs/html/design/media/wear/customlayout.png b/docs/html/design/media/wear/customlayout.png
new file mode 100644
index 0000000..6a3cd9a
--- /dev/null
+++ b/docs/html/design/media/wear/customlayout.png
Binary files differ
diff --git a/docs/html/design/media/wear/dismiss_cards.png b/docs/html/design/media/wear/dismiss_cards.png
new file mode 100644
index 0000000..2e2d53b8
--- /dev/null
+++ b/docs/html/design/media/wear/dismiss_cards.png
Binary files differ
diff --git a/docs/html/design/media/wear/expandable_stacks.png b/docs/html/design/media/wear/expandable_stacks.png
new file mode 100644
index 0000000..edc2456
--- /dev/null
+++ b/docs/html/design/media/wear/expandable_stacks.png
Binary files differ
diff --git a/docs/html/design/media/wear/fitness.png b/docs/html/design/media/wear/fitness.png
new file mode 100644
index 0000000..18ae969
--- /dev/null
+++ b/docs/html/design/media/wear/fitness.png
Binary files differ
diff --git a/docs/html/design/media/wear/five_seconds.gif b/docs/html/design/media/wear/five_seconds.gif
new file mode 100644
index 0000000..199ceb9
--- /dev/null
+++ b/docs/html/design/media/wear/five_seconds.gif
Binary files differ
diff --git a/docs/html/design/media/wear/gross_gestures.png b/docs/html/design/media/wear/gross_gestures.png
new file mode 100644
index 0000000..1faad9a
--- /dev/null
+++ b/docs/html/design/media/wear/gross_gestures.png
Binary files differ
diff --git a/docs/html/design/media/wear/low_info_card.png b/docs/html/design/media/wear/low_info_card.png
new file mode 100644
index 0000000..a3ebf16
--- /dev/null
+++ b/docs/html/design/media/wear/low_info_card.png
Binary files differ
diff --git a/docs/html/design/media/wear/peek-card.png b/docs/html/design/media/wear/peek-card.png
new file mode 100644
index 0000000..916f157
--- /dev/null
+++ b/docs/html/design/media/wear/peek-card.png
Binary files differ
diff --git a/docs/html/design/media/wear/round_noframe.png b/docs/html/design/media/wear/round_noframe.png
new file mode 100644
index 0000000..7a9759b
--- /dev/null
+++ b/docs/html/design/media/wear/round_noframe.png
Binary files differ
diff --git a/docs/html/design/media/wear/selection_list.png b/docs/html/design/media/wear/selection_list.png
new file mode 100644
index 0000000..dcb0745
--- /dev/null
+++ b/docs/html/design/media/wear/selection_list.png
Binary files differ
diff --git a/docs/html/design/media/wear/separate-info-cards.png b/docs/html/design/media/wear/separate-info-cards.png
new file mode 100644
index 0000000..59ee084
--- /dev/null
+++ b/docs/html/design/media/wear/separate-info-cards.png
Binary files differ
diff --git a/docs/html/design/media/wear/separate_info_cards.jpg b/docs/html/design/media/wear/separate_info_cards.jpg
new file mode 100644
index 0000000..d4cb386
--- /dev/null
+++ b/docs/html/design/media/wear/separate_info_cards.jpg
Binary files differ
diff --git a/docs/html/design/media/wear/separate_info_cards_1.jpg b/docs/html/design/media/wear/separate_info_cards_1.jpg
new file mode 100644
index 0000000..b987aea
--- /dev/null
+++ b/docs/html/design/media/wear/separate_info_cards_1.jpg
Binary files differ
diff --git a/docs/html/design/media/wear/separate_info_cards_2.jpg b/docs/html/design/media/wear/separate_info_cards_2.jpg
new file mode 100644
index 0000000..1930cb8
--- /dev/null
+++ b/docs/html/design/media/wear/separate_info_cards_2.jpg
Binary files differ
diff --git a/docs/html/design/media/wear/single_action_controls.jpg b/docs/html/design/media/wear/single_action_controls.jpg
new file mode 100644
index 0000000..ef89da0
--- /dev/null
+++ b/docs/html/design/media/wear/single_action_controls.jpg
Binary files differ
diff --git a/docs/html/design/media/wear/vision_music.png b/docs/html/design/media/wear/vision_music.png
new file mode 100644
index 0000000..b676d20
--- /dev/null
+++ b/docs/html/design/media/wear/vision_music.png
Binary files differ
diff --git a/docs/html/design/media/wear/vision_navigation.png b/docs/html/design/media/wear/vision_navigation.png
new file mode 100644
index 0000000..ef56977
--- /dev/null
+++ b/docs/html/design/media/wear/vision_navigation.png
Binary files differ
diff --git a/docs/html/design/media/wear/vision_traffic.png b/docs/html/design/media/wear/vision_traffic.png
new file mode 100644
index 0000000..2affa22
--- /dev/null
+++ b/docs/html/design/media/wear/vision_traffic.png
Binary files differ
diff --git a/docs/html/design/media/wear/vision_voice.png b/docs/html/design/media/wear/vision_voice.png
new file mode 100644
index 0000000..25fd49e
--- /dev/null
+++ b/docs/html/design/media/wear/vision_voice.png
Binary files differ
diff --git a/docs/html/design/media/wear/voice_commands.png b/docs/html/design/media/wear/voice_commands.png
new file mode 100644
index 0000000..9839ed8
--- /dev/null
+++ b/docs/html/design/media/wear/voice_commands.png
Binary files differ
diff --git a/docs/html/design/media/wear/wear_checkin.png b/docs/html/design/media/wear/wear_checkin.png
new file mode 100644
index 0000000..9aeb267
--- /dev/null
+++ b/docs/html/design/media/wear/wear_checkin.png
Binary files differ
diff --git a/docs/html/design/tv/images/apps-games-rows.png b/docs/html/design/tv/images/apps-games-rows.png
deleted file mode 100644
index 1724147..0000000
--- a/docs/html/design/tv/images/apps-games-rows.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/design/tv/images/atv-home.png b/docs/html/design/tv/images/atv-home.png
deleted file mode 100644
index 2c18827..0000000
--- a/docs/html/design/tv/images/atv-home.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/design/tv/images/atv.png b/docs/html/design/tv/images/atv.png
deleted file mode 100644
index cd96164..0000000
--- a/docs/html/design/tv/images/atv.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/design/tv/images/overscan.png b/docs/html/design/tv/images/overscan.png
deleted file mode 100644
index bf08dd8..0000000
--- a/docs/html/design/tv/images/overscan.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/design/tv/images/recommendations.png b/docs/html/design/tv/images/recommendations.png
deleted file mode 100644
index 579b390..0000000
--- a/docs/html/design/tv/images/recommendations.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/design/tv/images/search.png b/docs/html/design/tv/images/search.png
deleted file mode 100644
index be0d778..0000000
--- a/docs/html/design/tv/images/search.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/design/tv/images/settings.png b/docs/html/design/tv/images/settings.png
deleted file mode 100644
index f9f45fa..0000000
--- a/docs/html/design/tv/images/settings.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/design/tv/index.jd b/docs/html/design/tv/index.jd
index 2519e25..d144ff0 100644
--- a/docs/html/design/tv/index.jd
+++ b/docs/html/design/tv/index.jd
@@ -1,31 +1,15 @@
-page.title=Design for TV
-header.justLinks=1
-footer.hide=1
+page.title=Android TV
@jd:body
-<style>
-#landing-graphic-container {
- position: relative;
-}
-#text-overlay {
- position: absolute;
- left: 0;
- top: 402px;
- width: 220px;
-}
-</style>
+<img src="{@docRoot}design/media/android-tv.png"
+ width="460" style="float:right;margin:0 0 40px 40px" />
-<div id="landing-graphic-container">
- <div id="text-overlay">
- <span itemprop="description">
- Build beautiful apps for the biggest screen in the house.</span>
- <br><br>
- <a href="{@docRoot}design/tv/principles.html"
- class="landing-page-link">Design Principles</a>
- </div>
- <a href="{@docRoot}design/tv/principles.html">
- <img src="{@docRoot}design/tv/images/atv.png" style="margin-left: 70px;">
- </a>
-</div>
+<p>Android TV is <strong>coming soon</strong> and lets you engage your users in a new, shared environment.</p>
+
+<p>Users bring a specific set of expectations to the experience of watching TV, versus interacting
+with a phone or tablet. So find out how to get your app ready for its big-screen debut
+later this year by reading the
+<a href="{@docRoot}preview/tv/design/index.html">Android TV Design Guide</a>
+in the L Developer Preview.</p>
\ No newline at end of file
diff --git a/docs/html/design/tv/patterns.jd b/docs/html/design/tv/patterns.jd
deleted file mode 100644
index c8cc0b0..0000000
--- a/docs/html/design/tv/patterns.jd
+++ /dev/null
@@ -1,100 +0,0 @@
-page.title=Patterns for TV
-page.tags="design"
-@jd:body
-
-<p>As a developer of apps for TV, you should follow certain patterns to enable users to
- quickly understand and efficiently your app. This section describes recommended design patterns
- for TV apps.</p>
-
-<h2>Navigation</h2>
-
-<p>Users typically navigate TV devices using a directional pad (D-Pad). This type of controller
- limits movement to up, down, left, and right. In a typical D-Pad remote, hardware keys that
- correspond to those directions are present and an additional action key is available to make a
- selection. As you design your Android application for TVs, pay special attention to how users
- navigate your application when using a remote control instead of a touchscreen.</p>
-
-<p>[add visual: D-Pad image or illustration]</p>
-
-<p>A key aspect of making your application work well with a D-Pad controller is to make sure
- that there is always a object that is obviously in focus. If a user cannot see what is in focus,
- they will not be able to navigate your app intuitively with this type of controller.</p>
-
-<p>Optimize your app screen layouts for D-Pad navigation. Align objects in your app lists and
- grids to make navigation within each screen intuitive. Design your layout so it takes advantage of
- two-axis navigation.</p>
-
-
-<h2>Home and Back Buttons</h2>
-
-<p>
- In addition to the D-Pad buttons, Android TV devices always include Home and Back buttons on their
- controllers. Make sure the Back button functions within your app in a way that is consistent with
- the general <a href="{@docRoot}design/patterns/navigation.html">Android Design guidelines</a>.
-</p>
-
-
-<h2>Focus and Selection</h2>
-
-<p>Providing good focus and selection indicators is key to making your app useable on TV. As
- mentioned previously, making sure that an object is always selected in your app is critical for
- effective navigation using a D-Pad. This requirement also means that you must use focus indicators
- that are easy to recognize and should be consistent throughout your app.</p>
-
-<p>
- [add visual of selected item on screen]
-</p>
-
-<p>The default focus indicator used in Android TV use a combination of scale, shadow,
- brightness, and opacity. The focus feedback is enhanced by displaying an animation going from a
- non-focused to a focused state and back. Instead of immediately applying the focus transformation,
- it is animated into place to reduce abrupt changes and help users notice how the object changed.</p>
-
-<h2>Audio Feedback</h2>
-
-<p>Sounds on Android TV bring a cinematic quality to the interaction experience. You should
- consider adding sounds for user actions or to provide feedback when a user is only partially
- visually engaged with the screen (e.g., because they have their hands full or are multitasking).
- You should also consider using sounds as alternatives to error messages, for example to indicate
- that a user has reached the end of a list or is trying to navigate to an undefined location.</p>
-
-<h2>Banners</h2>
-
-<p>
- App Banners represent your app on the home screen of TV devices and serves and as a way for
- users to launch your app. Here are specific requirements for the banner image:
-</p>
-
-<ul>
- <li>Size: 320 x 180 px, xhdpi resource</li>
- <li>Text should be included in the image. If your app is available in more than one
- language, you must provide version of the banner image for each supported language.</li>
-</ul>
-
-
-<h2>App Icons</h2>
-
-<p>The app icon is shown in recommendation cards on the Home screen, search results and the main
- Browse screen of your app if you use {@code BrowseFragment}. Here are the specific
- requirements for the app icon:</p>
-
-<ul>
- <li><p>Full color: size: 52x52dp, PNG</p></li>
- <li><p>Monocolor: size 52x52dp, white(#fff) icon with transparent background, PNG</p></li>
-</ul>
-
-
-<h2>Background Images</h2>
-
-<p>Background images are displayed in the background of your app to provide additional visual
- interest, information or branding. The BrowseFragment and DetailsFragment classes in the Leanback
- support library provide specific support for background images and updating them as items are
- brought into and out of focus. Here are the specific requirements for background images:</p>
-
-<ul>
- <li>2016x1134 (1920x1080 + 5% extra margin for motion)</li>
-</ul>
-
-<p>
- <strong>Note:</strong> If the image does not meet this requirement, it is scaled to fit.
-</p>
\ No newline at end of file
diff --git a/docs/html/design/tv/ui-overview.jd b/docs/html/design/tv/ui-overview.jd
deleted file mode 100644
index c58c9cd..0000000
--- a/docs/html/design/tv/ui-overview.jd
+++ /dev/null
@@ -1,63 +0,0 @@
-page.title=UI Overview for TV
-page.tags="design"
-@jd:body
-
-<p>The Android TV system user interface provides the launch pad for your app's big screen
- experience. It's important to understand how your app is presented in the main user interface and
- how your app can help users get to the content they want quickly, including contributing content
- suggestions to the recommendations row.</p>
-
-<p>This section provides quick overview of the Android TV user interface.</p>
-
-
-<h2>Home Screen</h2>
-
-<p>The Home Screen is the start of a TV user's experience, providing search, content
- recommendations, access to apps and settings. The Home Screen provides a rich and cinematic
- overview of apps and content.</p>
-
-<img src="{@docRoot}design/tv/images/atv-home.png" alt="TV Home screen" />
-
-
-<h2>Search</h2>
-
-<p>By bringing the power of Google search to the big screen, Android TV makes new, dynamic
- connections between content - a favorite movie may connect to the discovery of a new music artist,
- planning trip to Paris might surface new YouTube content and photos.</p>
-
-<img src="{@docRoot}design/tv/images/search.png" alt="Recommendations Row" />
-
-
-<h2>Recommendations</h2>
-
-<p>The recommendation row on Android TV is a central feature of the Home Screen that allows
- users quick access to dynamic and relevant content for their media consumption activities. The
- stream is optimized for quick browsing of personalized content and activity resumption (on the
- device and across devices), while also providing a way for users to act on meaningful new content.</p>
-
-<img src="{@docRoot}design/tv/images/recommendations.png" alt="Recommendations Row" />
-
-<p>
- The recommendations are based on the user’s recent and frequent usage behaviors, as well as
- expressed content preferences. They are presented as cards that represent a system or app action,
- notification, activity, or piece of actionable media. Your app can provide suggestions for the
- recommendations row to help get your content noticed. To learn more, see
- <a href="{@docRoot}preview/tv/build-ui/recommendations.html">Recommendations</a>.
-</p>
-
-
-<h2>Apps and Games</h2>
-
-<p>Apps and Games rows both have special areas on the Home Screen. Within these respective
- areas, Apps and Games titles are reordered to reflect the user’s recent usage.</p>
-
-<img src="{@docRoot}design/tv/images/apps-games-rows.png" alt="Apps and Games Rows" />
-
-
-<h2>Settings</h2>
-
-<p>Access to Settings is found at the bottom of the Home Screen. From here, the user can access
- Android and device-specific settings. Please see the "Settings" section for more detailed
- information.</p>
-
-<img src="{@docRoot}design/tv/images/settings.png" alt="Settings Row" />
diff --git a/docs/html/design/wear/creative-vision.jd b/docs/html/design/wear/creative-vision.jd
new file mode 100644
index 0000000..aee115c
--- /dev/null
+++ b/docs/html/design/wear/creative-vision.jd
@@ -0,0 +1,56 @@
+page.title=Creative Vision for Android Wear
+@jd:body
+
+<style>
+div.span-13 {
+ margin:10px 0;
+}
+div.span-13 img {
+ float:left;
+ margin:2px 20px 40px 0;
+}
+div.span-13 p {
+ margin-left:167px;
+}
+div.span-13 h2 {
+ margin-top:0;
+ }
+</style>
+
+<p>Android Wear devices provide just the right information at just the right time,
+allowing users to be more connected to both the virtual world and the real world. Great Android
+Wear experiences are:</p>
+
+
+ <div class="layout-content-col span-13">
+ <img src="{@docRoot}design/media/wear/vision_traffic.png" width="147" height="147" />
+
+ <h2 id="Launched">Launched automatically</h2>
+ <p>Most people are used to launching apps by clicking an icon. Android Wear is different. Wearable apps are aware of the user’s context - time, location, physical activity, and so on. The apps use this information to insert cards into the stream when they become relevant. This makes Android Wear timely, relevant and very specific.</p>
+ </div>
+
+ <div class="layout-content-col span-13">
+ <img src="{@docRoot}design/media/wear/vision_navigation.png" width="147" height="147" />
+
+ <h2 id="Glanceable">Glanceable</h2>
+ <p>A classic wrist watch is designed to let you see the time in a split second and get on with what you were doing. Designing for Android Wear is no different. The less time it takes to use your software, the more time the user can be present in whatever they are doing. Android wear is fast, sharp and immediate.</p>
+ </div>
+
+ <div class="layout-content-col span-13">
+ <img src="{@docRoot}design/media/wear/vision_voice.png" width="147" height="147" style="border: 1px solid #ddd;" />
+
+ <h2 id="SuggestDemand">All about suggest and demand</h2>
+ <p>Android Wear is like a great personal assistant: it knows you and your preferences, it only interrupts you when absolutely necessary, and it’s always on hand to provide a ready answer. Android Wear is helpful, respectful, and responsive.</p>
+ </div>
+
+ <div class="layout-content-col span-13">
+ <img src="{@docRoot}design/media/wear/vision_music.png" width="147" height="147" />
+
+ <h2 id="Interaction">Zero or low interaction</h2>
+ <p>Staying true to the strengths afforded by a smaller form factor, Android Wear focuses on simple interactions, only requiring input by the user when absolutely necessary. Most inputs are based around touch swipes or voice, and inputs requiring fine-grained finger movements are avoided. Android Wear is gestural, simple, and fast.</p>
+ </div>
+
+<p>By providing a smart connection to the rest of the world while respecting the user’s attention, Android Wear feels personal and global, simple and smart, unobtrusive and ever-ready. Applications that represent these principles will feel most at home in the overall Android Wear experience.</p>
+
+<p>Third party apps extend Android Wear to be more specialized and helpful throughout the day. Installing apps are a way for the user to tell the Android Wear how to do that.</p>
+
diff --git a/docs/html/design/wear/index.jd b/docs/html/design/wear/index.jd
new file mode 100644
index 0000000..d6202d1
--- /dev/null
+++ b/docs/html/design/wear/index.jd
@@ -0,0 +1,69 @@
+page.title=Android Wear
+@jd:body
+
+
+
+<p>Designing apps for wearable devices powered by Android Wear
+is substantially different than designing for phones or
+tablets: different strengths and weaknesses, different use cases, different ergonomics.
+To get started, you should understand the overall vision for the Android Wear experience,
+and how apps fit into and enhance this experience.</p>
+
+<p>A new form factor deserves a new UI model. At a high level, the Android Wear UI consists of two
+main spaces centered around the core functions of <strong>Suggest</strong> and
+<strong>Demand</strong>. Your app will have an important role to play in both of these
+spaces.</p>
+
+
+
+<h2 id="Stream">Suggest: The Context Stream</h2>
+
+<div class="framed-wear-square" style="float:right;margin:0 -22px 60px 40px">
+ <img src="{@docRoot}wear/images/screens/stream.gif">
+</div>
+
+<p>The context stream is a vertical list of cards, each showing a useful or timely piece of information. Much like the Google Now feature on Android phones and tablets, users swipe vertically to navigate from card to card. Only one card is displayed at a time, and background photos are used to provide additional visual information. Your application can create cards and inject them into the stream when they are most likely to be useful.</p>
+
+<p>This UI model ensures that users don’t have to launch many different applications to check for updates; they can simply glance at their stream for a brief update on what’s important to them.</p>
+
+<p>Cards in the stream are more than simple notifications. They can be swiped horizontally to reveal additional
+<a href="{@docRoot}design/wear/patterns.html#Pages">pages</a>. Further horizontal swiping may reveal
+<a href="{@docRoot}design/wear/patterns.html#Actions">buttons</a>, allowing the user to take action on the notification. Cards can also be dismissed by swiping left to right, removing them from the stream until the next time the app has useful information to display.</p>
+
+
+
+
+<h2 id="CueCard">Demand: The Cue Card</h2>
+
+<div class="framed-wear-square" style="float:right;margin:0 -22px 60px 40px">
+ <img src="{@docRoot}wear/images/screens/cuecard.gif">
+</div>
+
+<p>For cases where Android Wear does not suggest an answer proactively through the context stream, the cue card allows users to speak to Google. The cue card is opened by saying, “OK Google” or by tapping on the background of the home screen. Swiping up on the cue card shows a list of suggested voice commands, which can also be tapped.</p>
+
+<p>At a technical level, each suggested voice command activates a specific type of intent. As a developer, you can match your applications to some of these intents so that users can complete tasks using these voice commands. Multiple applications may register for a single voice intent, and the user will have the opportunity to choose which application they prefer to use.</p>
+
+<p>Applications can respond to a voice command in the same way as they can respond to a tap on a regular in-stream action button: by adding or updating a stream card, or by launching a full screen application. Voice input often takes the form of a command, such as "remind me to get milk," in which case a simple confirmation animation is sufficient to display before automatically returning to the Context Stream.</p>
+
+
+<h2 id="Other">Other UI Features</h2>
+
+<ul>
+<li>The <strong>Home screen</strong> is the default state of the device and features:
+ <ul>
+ <li>The background, showing either content relating to the first card or a custom watch face design, depending on the watch face the user has chosen. Tapping anywhere on the background or saying "Ok Google" starts a voice query.
+ <li>Status indicators, showing connectivity, charging status, airplane mode, and in some watch faces a count of unread items.
+ <li>The top ranked card in the Context Stream, peeking up at the bottom of the screen. The amount of the peek card that appears is determined by the current watch face.
+ </ul>
+</li>
+
+<li><strong>Watch faces</strong> may be chosen by the user to appear in the background of the Home screen. Watch faces display the time and accommodate the top ranked peek card. The user can choose a different watch face by long pressing on the current one.</li>
+
+<li>Some devices may enter a low-power <strong>Ambient Mode</strong> when not being used. This usually involves dimming the screen in some way. The contents of a peek card will automatically be optimized for display in this state. Users can exit ambient mode by tapping on the screen, by tilting the screen towards them, or by pressing a hardware button if one exists.</li>
+
+<li>Swiping down on the Home screen reveals the <strong>Date and Battery</strong> display. Dragging further down toggles <strong>Mute mode</strong>, preventing interruptive notifications from vibrating and illuminating the screen.</li>
+
+<li>The <strong>Settings screen</strong> can be invoked from the cue card or on some devices using a hardware button. From here the user may shut down or restart their device, adjust screen brightness, toggle airplane mode, and access device information.</li>
+
+<li><strong>Full screen apps</strong> can be launched on top of the main stream where a wider range of interaction is called for. Although not stylistically limited to the context stream pattern, apps should respect the same design principles as the rest of the system. For more information, see the <a href="{@docRoot}design/wear/structure.html">App Structure</a> guide.</li>
+</ul>
\ No newline at end of file
diff --git a/docs/html/design/wear/patterns.jd b/docs/html/design/wear/patterns.jd
new file mode 100644
index 0000000..274b62f
--- /dev/null
+++ b/docs/html/design/wear/patterns.jd
@@ -0,0 +1,149 @@
+page.title=UI Patterns for Android Wear
+@jd:body
+
+
+
+<p>Android Wear is used for micro-interactions, and so adhering to consistent design patterns that users are already accustomed to is paramount.</p>
+
+<h2 id="Cards">Cards</h2>
+
+<p>Cards in the stream can take slightly different forms:</p>
+
+ <img src="{@docRoot}design/media/wear/Bluebird.png" width="147" height="147" style="float:left;margin:0 0 20px 0px">
+
+
+ <img src="{@docRoot}design/media/wear/single_action_controls.jpg" width="147" height="147" style="float:left;margin:0 0 20px 40px">
+
+
+ <img src="{@docRoot}design/media/wear/expandable_stacks.png" width="147" height="147" style="float:left;margin:0 0 20px 40px">
+
+<ul style="clear:both">
+<li>Standard cards for displaying information from a notification</li>
+<li>Single-action controls (such as a play/pause toggle)</li>
+<li>Expandable stack of cards, for grouping a set of related notifications together</li>
+</ul>
+
+
+<h2 id="Icons">App icons</h2>
+
+ <img src="{@docRoot}design/media/wear/clear_bold_type.jpg" width="147" height="147" style="float:right;margin:0 0 20px 40px">
+
+<p>App icons appear in a fixed position overhanging the edge at the top right of the card by default for all notifications in the Context Stream. This is an opportunity for cards to be recognized as coming from a specific source. Photo backgrounds should be used only to convey information, not to brand a card. App icons are necessary only on the leftmost card; it is not necessary to add the app icon to pages.</p>
+
+<h2 id="Pages" style="clear:both">Pages</h2>
+
+ <img src="{@docRoot}design/media/wear/separate-info-cards.png" height="147" style="float:right;margin:0 0 20px 40px">
+
+<p>Supplementary information should be displayed on additional cards to the right of a main Context Stream card. In most cases one additional detail card should be sufficient. For example, a weather card might show the weather for the current location today, with subsequent days listed in an additional card to the right. Keep the number of detail cards as low as possible. Actions (see below) should always come after pages; don’t change the order or interleave them.</p>
+
+
+<a class="notice-developers left" href="{@docRoot}training/wearables/notifications/pages.html">
+ <div>
+ <h3>Developer Docs</h3>
+ <p>Adding Pages to a Notification</p>
+ </div>
+</a>
+
+<h2 id="Dismissing" style="clear:both">Dismissing cards</h2>
+
+ <img src="{@docRoot}design/media/wear/dismiss_cards.png" height="147">
+
+<p>Swiping from left to right on a card causes it to be dismissed from the stream. Dismissed cards may return when they next have relevant information. State is synced between the Android Wear context stream and the notifications on the Android handheld device, so dismissing from one causes an automatic dismissal from the other.</p>
+
+
+
+<h2 id="Actions" style="clear:both">Action buttons</h2>
+
+ <img src="{@docRoot}design/media/wear/action_button.png" width="147" height="147" style="float:right;margin:0 0 20px 40px">
+
+<p>Where the user may need to take action on the information shown in a notification, you can provide action buttons. These are system-rendered buttons that appear to the right of detail cards. They consist of a white icon set on a blue system-rendered circular button and a short caption with a verb. Actions should be limited to three for a single card row.</p>
+
+<p>Tapping on an action button can cause an action to be executed; or an action to be continued on the companion handheld; or a full screen activity to be invoked for further input (see the <a href="#2DPicker">2D Picker</a> section below).</p>
+
+<p>Refer to the UI Toolkit provided in the <a href="{@docRoot}design/downloads/index.html#Wear">Downloads</a> page for detailed specs regarding action icons.</p>
+
+
+<h2 id="Countdown" style="clear:both">Action countdown and confirmation</h2>
+
+ <img src="{@docRoot}design/media/wear/countdown.png" width="149" height="149" style="float:right;margin:0 0 20px 40px">
+
+<p>Where tapping on an action button results in an action being executed, one of the following can happen:</p>
+
+<ol>
+<li>The action is completed immediately and the result of the action is shown (either by updating the relevant card contents immediately, or by showing a confirmation animation).</li>
+<li>A short countdown animation to completing the action is played, which the user can interrupt to cancel. Once the timer has counted down, a confirmation animation is played. This animation can be custom-designed by developers.</li>
+<li>A confirmation step is required. This is for actions that are potentially damaging if accidentally triggered. A generic confirmation template is supplied by the system. Once the user confirms, the standard confirmation animation is played.</li>
+<li>The cue card can be invoked to continue specifying the action. For example in a messaging application, tapping a “Reply” action button invokes the Cue Card and prompts for voice input. In this case the prompt label (such as “Speak your message…”) and a set of sample voice suggestions can be specified by developers.</li>
+</ol>
+
+
+<h2 id="Continuing" style="clear:both">Continuing activities on phone</h2>
+
+ <img src="{@docRoot}design/media/wear/continue_phone.png" width="147" height="147" style="float:right;margin:0 0 20px 40px">
+
+<p>Developers should attempt to perform actions on the wearable device wherever possible. In cases where the phone must be used, a generic animation should be played once the action button has been tapped and the corresponding Android app will open on the phone.</p>
+
+
+<h2 id="ActionOnCard" style="clear:both">Actions on cards (such as media controls)</h2>
+
+ <img src="{@docRoot}design/media/wear/action_on_card.png" width="147" height="147" style="float:right;margin:0 0 20px 40px">
+
+<p>Some cards may benefit from having tappable actions directly on a card. Some guidance on when to use this pattern versus using an action button:</p>
+
+<ul>
+<li>This pattern should be used when only one possible action could be reasonably expected. For example, tapping on an address with a car icon and ETA seems like it would very obviously launch directions. Conversely, if you see a contact's photo and name, it's not clear what tapping would do (call them? email them?), so the pattern shouldn't be used in this case.</li>
+<li>On-card actions should not require a text label to be understood.</li>
+<li>On-card actions should only result in something happening on the wearable (apart from web links to open them on the phone).</li>
+<li>Only one action per card: no menus on a single card.</li>
+</ul>
+
+<p>Good examples of using an action on card include: play / pause music; toggle light switch on and off; navigate to an address; call a phone number.</p>
+
+
+<h2 id="Stacks" style="clear:both">Card stacks</h2>
+ <img src="{@docRoot}design/media/wear/expandable_stacks.png" width="147" height="147" style="float:right;margin:0 0 20px 40px">
+<p>Card stacks group related cards together and allow them to be progressively expanded vertically in the stream. A tap on a stack fans the cards out so that the top edge of each card can be seen. A subsequent tap on a fanned card reveals that card fully. Stacks of cards revert to a fully collapsed state once the user has swiped away from them.</p>
+
+
+<a class="notice-developers left" style="clear:none" href="{@docRoot}training/wearables/notifications/stacks.html">
+ <div>
+ <h3>Developer Docs</h3>
+ <p>Stacking Notifications</p>
+ </div>
+</a>
+
+
+<h2 id="2DPicker" style="clear:both">2D Picker</h2>
+
+<p>A 2D Picker component in your app can be invoked from the cue card or from an action button. It allows users to choose from a list of items, and optionally select an attribute of each item. For example, in response to a voice action to “buy tickets to a movie tonight,” you could show a 2D Picker with a vertical list of movies playing, with each movie having a horizontal list of showtimes.</p>
+
+<img src="{@docRoot}design/media/wear/2D_picker_action.png" width="500" alt="">
+
+<p>In some instances, further information may be required. In these cases, the most probable default values for these choices should be chosen on the user’s behalf with the option to edit before completing the action. This pattern is in keeping with Android Wear’s core design principle of minimizing interactions required.</p>
+
+<p>More information about how to use the 2D Picker pattern is provided in the <a href="{@docRoot}design/wear/structure.html#2DPicker">App Structure</a> guide.</p>
+
+
+<h2 id="Voice" style="clear:both">Voice commands</h2>
+
+ <img src="{@docRoot}design/media/wear/voice_commands.png" width="147" height="147" style="float:right;margin:0 0 20px 40px">
+
+<p>It is possible for apps to take action in response to Android voice commands that invoke intents. For example, an app can register for the “Take a note” intent and capture the subsequent voice input for processing. In the case where multiple apps registered for the same intent, user preference will be captured once and saved. Users can edit their intent preferences in the Android Wear app on their handheld.</p>
+
+<a class="notice-developers left" style="clear:none" href="{@docRoot}training/wearables/apps/voice.html">
+ <div>
+ <h3>Developer Docs</h3>
+ <p>Adding Voice Capabilities</p>
+ </div>
+</a>
+
+
+<h2 id="Selection" style="clear:both">Selection List</h2>
+
+ <img src="{@docRoot}design/media/wear/selection_list.png" width="147" height="147" style="float:right;margin:0 0 20px 40px;border:1px solid #ddd">
+
+<p>Choosing an item from a list is a common interaction. The Selection List pattern (available as the <a
+href="{@docRoot}training/wearables/apps/layouts.html#UiLibrary"><code>WearableListView</code></a> component) creates a simple list optimized for ease of use on a small screen: the focused item snaps to the center of the screen, and a single tap selects. This widget is recommended as a common pattern for selecting items. It is used throughout the system UI, including in the list that can be accessed by swiping up on the cue card.</p>
+
+
+<p>Of course, it is possible for Android Wear apps to extend themselves beyond the familiarities of these patterns. For a deeper look at the options available, see the <a href="{@docRoot}design/wear/structure.html">App Structure</a> guide.</p>
diff --git a/docs/html/design/wear/principles.jd b/docs/html/design/wear/principles.jd
new file mode 100644
index 0000000..6286a92
--- /dev/null
+++ b/docs/html/design/wear/principles.jd
@@ -0,0 +1,75 @@
+page.title=Design Principles for Android Wear
+@jd:body
+
+<style>
+p.try {
+ color:#888;
+}
+p.try b {
+font-size: 22px;
+font-weight: 200;
+padding: 0 0 5px;
+display: block;
+}
+h2 {
+ margin-top:50px;
+}
+</style>
+
+<p>These design principles provide some simple heuristics about how you should plan and assess your
+Android Wear app design.</p>
+
+
+<h2>Focus on not stopping the user and all else will follow</h2>
+
+<img src="{@docRoot}design/media/wear/five_seconds.gif" height="147" style="float:right;margin:10px 0 0 40px" />
+<p class="img-caption" style="width:315px;float:right;clear:right;margin:5px 0 30px 40px">
+The time required for each action on the left is 5 seconds.</p>
+
+<p>A watch is a perfect form factor for a device that you can use while doing something else, such as cooking, eating, walking, running, or even having a conversation. If using your wearable app causes the user to stop whatever they’re doing, it’s a good occasion to consider how to improve it using the principles in this section.</p>
+
+<p class="try" ><b>Try this:</b> Time a typical use of your Wear app. If using it takes more than 5 seconds, you should think about making your app more focused. Also try using your app while you’re having a conversation, and see how it affects your train of thought and eye contact.</p>
+
+
+<h2 id="BigGestures">Design for big gestures</h2>
+
+<img src="{@docRoot}design/media/wear/biggesture.png" alt="" height="147" style="float:right;margin:10px 0 0 40px" />
+<p class="img-caption" style="width:315px;float:right;clear:right;margin:5px 0 30px 40px">
+Use few and large touch targets.</p>
+
+<p>When you swipe through photos on your phone you’re using a large area of the display, and you don’t have to be precise at all. That’s the best kind of interaction for a wearable device. Your users are going to use your app in all sorts of situations, the least frequent one might actually be sitting down at their desk.</p>
+
+<p class="try"><b>Try this:</b> Use your app in various everyday situations, such as walking, eating, talking to people, or ordering coffee. If you have to slow down while walking or stop the conversation to be precise, you should consider how your gestures could be bigger.</p>
+
+
+<h2 id="CardsFirst">Think about stream cards first</h2>
+
+<p class="img-caption" style="width:150px;float:right;clear:right;margin:10px 0 30px 25px">An app that offers to check in users could appear in the stream suggesting the most likely place nearby, after a certain amount of time.</p>
+
+<img src="{@docRoot}design/media/wear/wear_checkin.png" style="float:right;margin:10px 0 40px 40px" width="147" height="147">
+
+<p>The best experience on a wearable device is when the right content is there just when the user needs it. You can figure out when to show your cards with sensors, or events happening in the cloud. For the cases where it’s impossible to know when the user needs your app, you can rely on a voice action or touch.</p>
+
+<p class="try"><b>Try this:</b> Make a list of all the situations a user would find your app useful. What do they have in common? Same location? Time of day? Certain physical activities? You will most likely come up with several different situations - that’s a good sign, because it means that you can specialize your cards to those situations. Remember that the user always has the option of completely muting your stream cards if they feel they aren’t relevant enough.</p>
+
+
+
+<h2 id="Fast">Do one thing, really fast</h2>
+
+<p>While users will engage with your app for only a few seconds at time, they'll use it many times throughout the day. A well-designed stream card carries one bit of information and potentially offers a few action buttons when the user swipes over.</p>
+
+<p class="try"><b>Try this:</b> How many bits of information is there in your design? Is everything absolutely necessary, or could you split it up into separate cards? If you’re designing a card, don’t forget that you can use multiple pages.</p>
+
+
+<h2 id="CornerOfEye">Design for the corner of the eye</h2>
+
+<p>The longer the user is looking at your app, the more you are pulling them out of the real world. Thinking about how to design your app for glanceability can vastly help the user get full value from your app and quickly go back to what they were doing.</p>
+
+<p class="try"><b>Try this:</b> To view your app with your peripheral vision, try focusing on your knuckles while your watch is displaying the app. Do you get a sense of what it is trying to do? Is it distinguishable from other apps? Does the background image help conveying the message? Does it use photos or a distinct shape and color?</p>
+
+
+<h2 id="Tapper">Don’t be a constant shoulder tapper</h2>
+
+<p>A watch constantly touches the user’s skin. Being this intimate, you want to buzz the watch fewer times than you’re used to on the phone.</p>
+
+<p class="try"><b>Try this:</b> Next time you’re in a conversation, imagine someone tapping you your shoulder, interrupting you with the information you want your app to deliver. If the information delivered did not justify suspending a conversation, you should not make the notification interruptive.</p>
\ No newline at end of file
diff --git a/docs/html/design/wear/structure.jd b/docs/html/design/wear/structure.jd
new file mode 100644
index 0000000..67b218a
--- /dev/null
+++ b/docs/html/design/wear/structure.jd
@@ -0,0 +1,129 @@
+page.title=App Structure for Android Wear
+@jd:body
+
+
+<p>Users are used to tapping icons to launch apps. Android Wear is different. A typical Wear app adds a card to the stream at a contextually relevant moment. It might have a button that opens a full screen view for a fast micro interaction, like below, but it just as well might not.
+</p>
+
+<img src="{@docRoot}design/media/wear/appstructuresample.png" alt="" />
+
+<p>These are the building blocks, ordered by simplicity. You can use one of them or some of them, but we strongly recommend not building apps the user has to launch and quit before thinking really hard about how you could react to a specific location, activity, time of day, or something happening in the cloud.</p>
+
+
+<ul>
+ <li><strong>Contextual card in the stream</strong>:
+ <ul>
+ <li><a href="#Bridged"><strong>Bridged notifications</strong></a> are pushed to the wearable from the connected handheld such as new message notifications, using standard Android notifications. These require little or no code on the wearable.
+ <li><a href="#Contextual"><strong>Contextual notifications</strong></a> are generated locally on the wearable and appear at contextually relevant moments such as an exercise card that appears when you’re going on a run. You can do more with this kind of card than with a notification bridged from the handheld.
+ </ul>
+ </li>
+ <li><strong>Full screen UI app</strong>:
+ <ul>
+ <li><a href="#2DPicker"><strong>2D Picker</strong></a> is design pattern that allows the user to select from a set of items, such as choosing artists and then albums. We recommend using this pre-built component from the SDK whenever relevant.
+ <li><a href="#Custom"><strong>Custom layouts</strong></a> are also possible where apps need to extend beyond the basic card/stream metaphor.</li>
+ </ul>
+ </li>
+</ul>
+
+<p>Many applications will consist of a combination of these views, possibly with connections between them. For example, a contextual card may have an action that launches a more immersive experience. Inversely, an immersive experience may result in a card being added to the stream.</p>
+
+
+<h2 id="Bridged">Bridged Notifications</h2>
+
+<img src="{@docRoot}design/media/wear/bridgednotifications.jpg" height="147" style="float:right;margin:0 0 20px 20px" alt="">
+<img src="{@docRoot}design/media/wear/Bluebird.png" height="147" style="float:right;margin:0 0 20px 40px" alt="">
+
+<p>This is the simplest way to get on Android Wear. In fact, your app already does this if it uses notifications. You can add Wear-specific features like extra pages and voice replies by using the new notification APIs. </p>
+
+<a class="notice-developers left" style="clear:none" href="{@docRoot}training/wearables/notifications/creating.html">
+ <div>
+ <h3>Developer Docs</h3>
+ <p>Creating a Notification</p>
+ </div>
+</a>
+
+
+<h2 id="Contextual" style="clear:both">Contextual Notifications</h2>
+
+
+<img src="{@docRoot}design/media/wear/context_workout.png" width="323" height="147" style="float:right;margin:0 0 20px 40px;clear:right" alt="" />
+<img src="{@docRoot}design/media/wear/context_lights.png" width="323" height="147" style="float:right;margin:0 0 20px 40px;clear:right" alt="" />
+
+<p>This is what Android Wear does best; showing users information just when they need it.</p>
+
+<p>Here’s how it works: Your app knows when it is relevant for the user, and when it happens, you trigger a contextual notification. Maybe you’re building a running app that’s relevant when the user is running. Maybe it’s a museum guide that’s relevant when the user is visiting a particular museum. Check out the design principles for more on thinking about your app contextually. </p>
+
+<p>Getting contextual triggering right is one of the most impactful things you can do to craft a great user experience. </p>
+
+<p>The easiest way to do this is to use standard templates for Android notifications. If you decide to make your own ActivityView, we strongly recommend you take a look at the <a href="{@docRoot}design/wear/style.html">Style</a> guide to make sure you stay consistent with the rest of the device. </p>
+
+<p>Don’t forget to test your triggering thoroughly. Triggering too often can be so annoying that users might end up blocking all your notifications. </p>
+
+
+
+<h2 id="2DPicker">2D Picker</h2>
+
+<p>The 2D Picker design pattern (available as the <a href="{@docRoot}training/wearables/apps/layouts.html#UiLibrary">GridViewPager component</a>) is useful for showing a range of options or asking a user to make a quick selection. Google search results on Android Wear are a great example of the GridViewPager pattern in action.</p>
+
+<img src="{@docRoot}design/media/wear/1D_picker.png" alt="" width="499px" />
+<p class="img-caption">This pattern can be used to present a single vertical list, or a “1D Picker”</p>
+
+<img src="{@docRoot}design/media/wear/2D_picker.png" alt="" width:760px" />
+<p class="img-caption">It can also be used as a 2D matrix of options, as a way of presenting categorized options.</p>
+
+
+<img src="{@docRoot}design/media/wear/2d_picker_indicated.png" alt="" width="760px" />
+<p class="img-caption">Navigation should be vertical-then-horizontal, not horizontal-then-vertical, and limit the the vertical set to around five cards.</p>
+
+
+<h3>Actions</h3>
+<p>For actions on each cards, use the <a href="{@docRoot}design/wear/patterns.html#Actions">Action cards pattern</a>.</p>
+
+
+<h3>Exiting</h3>
+<p>2D Picker should be automatically dismissed when a selection is made. It may also be exited by swiping back down on the first card, or by swiping left to right on a leftmost card.</p>
+
+<h3>Making it fast</h3>
+<p>A few of our favorite tips on how to make the 2D picker really fast for your users:</p>
+<ul>
+<li>Minimize the number of cards</li>
+<li>Show the most popular card at the top </li>
+<li>Keep the cards extremely simple</li>
+<li>Optimize for speed over customization</li>
+
+</ul>
+
+
+
+<h2 id="Custom">Breaking out of the card (with custom layouts)</h2>
+
+<a class="notice-developers" href="{@docRoot}training/wearables/apps/index.html">
+ <div>
+ <h3>Developer Docs</h3>
+ <p>Creating Wearable Apps</p>
+ </div>
+</a>
+
+<p>There are some things you can’t do on a card. Swiping in many directions on a map or a joystick are a few examples. In those cases it might be good idea to momentarily go full screen.</p>
+
+<img src="{@docRoot}design/media/wear/customlayout.png" alt="" width="760px" />
+
+<h3>When to go full screen</h3>
+<p>We highly recommend only going full screen when you can’t do what you want on a card, and quickly exit back to the stream the moment you’re done. That way your app will feel like an integrated part of the system.</p>
+
+<h3>Be distinct</h3>
+<p>Your full screen design shouldn’t look too much like the card stream or it could confuse users. The 2D picker is always available if you need a card-like UI.</p>
+
+<h3>Automatically exiting</h3>
+<p>Many devices don’t have back or home buttons, so exiting is your responsibility as the app designer. Here’s a few examples of natural exits:</p>
+<ul>
+ <li>A map that asks the user to drop a pin should exit when it happens</li>
+ <li>A short game can exit when the game finishes</li>
+ <li>A drawing app can finish after 5 seconds of inactivity.</li>
+</ul>
+
+<h3>Manually exiting</h3>
+<p>Even with logical exit points like these, some cases may exist where the user may want to immediately exit. This may be common in apps that are used for a longer while. In all cases, the developer should present the option to quit the app on long press using
+<a href="{@docRoot}training/wearables/apps/layouts.html#UiLibrary"><code>DismissOverlayView</code></a>. Your design should reserve long press for the sole purpose of prompting to quit.</p>
+
+
diff --git a/docs/html/design/wear/style.jd b/docs/html/design/wear/style.jd
new file mode 100644
index 0000000..948b934
--- /dev/null
+++ b/docs/html/design/wear/style.jd
@@ -0,0 +1,115 @@
+page.title=Style for Android Wear
+@jd:body
+
+
+<p>Here are a number of design considerations to bear in mind that are particular to Android Wear.</p>
+
+
+
+<img src="{@docRoot}design/media/wear/circle_message2.png" height="200"
+ style="float:right;margin:32px 0 50px 40px;">
+
+<img src="{@docRoot}design/media/wear/fitness.png" height="200"
+ style="float:right;margin:32px 0 50px 40px;">
+
+<h2 id="ScreenSize">Screen Size</h2>
+
+<p>Be mindful of different device sizes and shapes. Wearable devices are a form of fashion and expression for their owners, and so Android Wear supports a variety of forms. Most of the complexities of supporting these different devices is taken care of at a system level, but bear in mind different screen types when designing custom full screen apps.</p>
+
+<p>Use the Android Wear emulator to test both square and round devices, and note that <a
+href="{@docRoot}training/wearables/apps/layouts.html#UiLibrary"><code>WatchViewStub</code></a> is available to activities to detect whether a square or round device is being used.</p>
+
+
+
+<div class="layout-content-row">
+ <div class="layout-content-col span-6">
+
+ <img src="{@docRoot}design/media/wear/assets_specifics.png" width="300"
+ style="margin:32px 0 20px;">
+ </div>
+ <div class="layout-content-col span-7">
+ <h2 id="Assets" style="clear:both">Specific Assets Required</h2>
+
+ <p>A core set of standard assets may need to be provided depending on your card design: app icon, background image or images, action icons, actions confirmation animation. Of course, your specific design may necessitate other assets. Background image should be provided in landscape format at least 600px width for notifications that include pages of cards, since the system automatically adds a parallaxing effect.</p>
+ </div>
+</div>
+
+<div class="layout-content-row">
+ <div class="layout-content-col span-6">
+ <h2 id="PeekCard" style="clear:both">Peek Card Readability</h2>
+
+ <p>Test your card layout to ensure that useful information is conveyed in the peek state on the Home screen. The main message of the card should be readable in the peek state, particularly for contextual cards. Content that requires an interaction to be read, for example a long message, should be cropped appropriately to provide an affordance to the user to swipe the card to read more.</p>
+ </div>
+ <div class="layout-content-col span-6">
+
+ <img src="{@docRoot}design/media/wear/peek-card.png" width="300"
+ style="margin:12px 0 0 20px">
+ </div>
+</div>
+
+
+
+ <img src="{@docRoot}design/media/wear/low_info_card.png" width="147" height="147"
+ style="float:right;margin:29px 0 20px 40px">
+
+<h2 id="InfoDensity" style="margin-top:0" >Low Information Density</h2>
+
+<p>Cards should be designed to be glanceable in a split second, just like reading the time on a traditional watch. In most cases a pairing of an icon and value, or a title and short caption should be enough to convey a meaningful message. Note that the background photo should also be used to convey information; backgrounds that change to reflect and support the primary message in the card work great. For example, in the case illustrated above a suitable background image is chosen to reflect severity of the current traffic conditions. This is not just a nice piece of attention to detail; the background actually reinforces the message and makes the content more glanceable.</p>
+
+
+<img src="{@docRoot}design/media/wear/separate_info_cards.jpg" height="147"
+ style="float:right;margin:29px 0 20px 40px">
+
+<h2 id="Chunks">Separate Information into Chunks</h2>
+
+<p>In cases where additional information is absolutely necessary, don’t crowd out a card layout to the point where glanceability is affected. Instead, add an additional <a href="{@docRoot}design/wear/patterns.html#Pages">page</a> (or multiple pages, if needed) to the right of the main card in the stream to which the user can swipe for more information. See also <a
+href="{@docRoot}design/wear/patterns.html#Continuing">Continuing activities on phone</a>.</p>
+
+
+<h2 id="KeepMinimum" style="clear:both">Keep Notifications to a Minimum</h2>
+
+<p>Don’t abuse the user’s attention. Active notifications (that is, those that cause the device to vibrate) should only be used in cases that are both timely and involve a contact, for example receiving a message from a friend. Non-urgent notifications should be silently added to the Context Stream. See also the general Android Notifications Guidelines.</p>
+
+
+
+
+<img src="{@docRoot}design/media/wear/clear_bold_type.jpg" height="147"
+ style="float:left;margin:19px 40px 20px 0">
+
+<h2 id="Typography" >Use Clear, Bold Typography</h2>
+
+<p>The system font is Roboto Condensed, with Regular and Light variants. Text should adhere to the size and color recommendations (see the UI Toolkit in the <a href="{@docRoot}design/downloads/index.html#Wear">Downloads</a> page). In general, text should be displayed as large as possible. Your goal should be to convey maximum information with minimum fuss.</p>
+
+
+<h2 id="Branding" style="clear:both" >Use Consistent Branding and Color</h2>
+
+<p>The app icon is used to identify and brand your application. The icon is optional but when present always appears in the same location, overhanging the top edge of the card at the right. Note that app icons or branding should not be displayed in the background photo, which is reserved to display an image relevant to the information on the card.</p>
+
+
+<img src="{@docRoot}design/media/wear/copywrite.png" height="147"
+ style="float:left;margin:19px 40px 20px 0">
+
+<h2 id="Copywrite" >Copywrite Sparingly</h2>
+
+<p>Omit needless text. Design for glanceability, not reading. Use words and phrases, not sentences. Use icons paired with values instead of text wherever possible. Text strings should be as concise as possible, and long pieces of text will be truncated to fit on a single card.</p>
+
+
+<h2 id="BeDiscreet" style="clear:both" >Be Discreet if Necessary</h2>
+
+<p>Wearables are personal devices by nature, but they are not completely private. If your notification serves content that may be particularly sensitive or embarrassing (such as notifications from a dating app or a medical status report), consider not displaying all of the information in a peek card. A notification could place the sensitive information on a second page that must be swiped to, or an application could show different amounts of detail in peek and focused card positions.</p>
+
+
+ <img src="{@docRoot}design/media/wear/confirmation.png" height="147"
+ style="float:left;margin:29px 40px 20px 0">
+
+
+<h2 id="ConfirmAnim">Confirmation Animations</h2>
+
+<p>If your app allows the user to perform an action, it is necessary to provide positive feedback. Show a generic confirmation animation or create your own. A confirmation animation is an opportunity to express your app’s character and insert a moment of delight for your user. Keep animations short (less than 1000ms) and simple. Animating the confirmation icon is an effective way of transitions the user to a new state after completing an action.</p>
+
+
+
+
+
+
+
diff --git a/docs/html/google/gcm/ccs.jd b/docs/html/google/gcm/ccs.jd
index 03addfd..4389e3d 100644
--- a/docs/html/google/gcm/ccs.jd
+++ b/docs/html/google/gcm/ccs.jd
@@ -19,7 +19,11 @@
<li><a href="#response">Response format</a></li>
</ol>
</li>
- <li><a href="#upstream">Upstream Messages</a> </li>
+ <li><a href="#upstream">Upstream Messages</a>
+ <ol>
+ <li><a href="#receipts">Receive return receipts</a></li>
+ </ol>
+ </li>
<li><a href="#flow">Flow Control</a> </li>
<li><a href="#implement">Implementing an XMPP-based App Server</a>
<ol class="toc">
@@ -43,9 +47,6 @@
</div>
</div>
-<p class="note"><strong>Note:</strong> To try out this feature, sign up using
-<a href="https://services.google.com/fb/forms/gcm/">this form</a>.</p>
-
<p>The GCM Cloud Connection Server (CCS) is an XMPP endpoint that provides a
persistent, asynchronous, bidirectional connection to Google servers. The
connection can be used to send and receive messages between your server and
@@ -149,8 +150,8 @@
<li>CCS adds the field {@code message_id}, which is required. This ID uniquely
identifies the message in an XMPP connection. The ACK or NACK from CCS uses the
{@code message_id} to identify a message sent from 3rd-party app servers to CCS.
-Therefore, it's important that this {@code message_id} not only be unique, but
-always present.</li>
+Therefore, it's important that this {@code message_id} not only be unique (per
+sender ID), but always present.</li>
</ul>
<p>In addition to regular GCM messages, control messages are sent, indicated by
@@ -188,7 +189,8 @@
"hello":"world",
}
"time_to_live":"600",
- "delay_while_idle": true/false
+ "delay_while_idle": true/false,
+ "delivery_receipt_requested": true/false
}
</gcm>
</message>
@@ -227,42 +229,48 @@
<p>Below are some examples.</p>
<p>Bad registration:</p>
+
<pre><message>
- <data:gcm xmlns:data="google:mobile:data">
+ <gcm xmlns="google:mobile:data">
{
- "error":"BAD_REGISTRATION", // error code
+ "message_type":"nack",
"message_id":"msgId1",
- "from":"PA91bHFOtaQGSwupt5l1og",
- "message_type":"nack"
+ "from":"SomeInvalidRegistrationId",
+ "error":"BAD_REGISTRATION",
+ "error_description":"Invalid token on 'to' field: SomeInvalidRegistrationId"
}
- </data:gcm>
+ </gcm>
</message></pre>
-<p>Invalid "time to live":</p>
+<p>Invalid JSON:</p>
<pre><message>
- <data:gcm xmlns:data="google:mobile:data">
- {
- "error":"InvalidJson : INVALID_TTL : Invalid value (-1) for \"time_to_live\": must be between 0 and \"2419200\"\n",
- "message_id":"msgId1",
- "from":"APA91bHFOtaQGSwupt5l1og",
- "message_type":"nack"
- }
- </data:gcm>
-</message></pre>
+ <gcm xmlns="google:mobile:data">
+ {
+ "message_type":"nack",
+ "message_id":"msgId1",
+ "from":"APA91bHFOtaQGSwupt5l1og",
+ "error":"INVALID_JSON",
+ "error_description":"InvalidJson: JSON_TYPE_ERROR : Field \"time_to_live\" must be a JSON java.lang.Number: abc"
+ }
+ </gcm>
+</message>
+</pre>
-<p>JSON type error:</p>
+<p>Quota exceeded:</p>
<pre><message>
- <data:gcm xmlns:data="google:mobile:data">
- {
- "error":"InvalidJson : JSON_TYPE_ERROR : Field \"delay_while_idle\" must be a JSON java.lang.Boolean: not-boolean-user-supplied-value\n",
- "message_id":"msgId1",
- "from":"APA91bHFOtaQGSwupt5l1og",
- "message_type":"nack"
- }
- </data:gcm>
-</message></pre>
+ <gcm xmlns="google:mobile:data">
+ {
+ "message_type":"nack",
+ "message_id":"msgId1",
+ "from":"APA91bHFOtaQGSwupt5l1og",
+ "error":"QUOTA_EXCEEDED",
+ "error_description":"Short-term downstream quota exceeded for this registration id"
+ }
+ </gcm>
+</message>
+</pre>
<p>The following table lists NACK error codes. Unless otherwise
@@ -300,7 +308,7 @@
</tr>
<tr>
<td>{@code INVALID_JSON}</td>
-<td>The JSON message payload was not valid.</td>
+<td>The JSON message payload is not valid.</td>
</tr>
<tr>
<td>{@code QUOTA_EXCEEDED}</td>
@@ -309,10 +317,10 @@
rate.</td>
</tr>
<tr>
-<td>{@code SERVICE_UNAVAILABLE}</td>
-<td>CCS is not currently able to process the message. The
-message should be retried over the same connection using exponential backoff
-with an initial delay of 1 second.</td>
+ <td>{@code SERVICE_UNAVAILABLE}</td>
+ <td>CCS is not currently able to process the message. The
+ message should be retried over the same connection using exponential backoff
+ with an initial delay of 1 second.</td>
</tr>
</table>
@@ -382,8 +390,8 @@
// Bundle data consists of a key-value pair
data.putString("hello", "world");
// "time to live" parameter
-// This is optional. It specifies a value in seconds up to 4 weeks.
-int ttl = [0 seconds, 4 weeks]
+// This is optional. It specifies a value in seconds up to 24 hours.
+int ttl = [0 seconds, 24 hours]
gcm.send(GCM_SENDER_ID + "@gcm.googleapis.com", id, ttl, data);
</pre>
@@ -419,6 +427,69 @@
</gcm>
</message></pre>
+<h3 id="receipts">Receive return receipts</h3>
+
+<p>You can use upstream messaging to get receipt notifications, confirming
+that a given message was sent to a device. Your 3rd-party app server receives the receipt
+notification from CCS once the message has been sent to the device.</p>
+
+<p>To enable this feature, the message your 3rd-party app server sends to CCS must include
+a field called <code>"delivery_receipt_requested"</code>. When this field is set to
+<code>true</code>, CCS sends a return receipt. Here is an XMPP stanza containing a JSON
+message with <code>"delivery_receipt_requested"</code> set to <code>true</code>:</p>
+
+<pre><message id="">
+ <gcm xmlns="google:mobile:data">
+ {
+ "to":"REGISTRATION_ID",
+ "message_id":"m-1366082849205"
+ "data":
+ {
+ "hello":"world",
+ }
+ "time_to_live":"600",
+ "delay_while_idle": true,
+ <strong>"delivery_receipt_requested": true</strong>
+ }
+ </gcm>
+</message>
+</pre>
+
+<p>Here is an example of a receipt notification message that CCS sends back to your 3rd-party
+app server:</p>
+
+</p>
+<pre><message id="">
+ <gcm xmlns="google:mobile:data">
+ {
+ "category":"com.example.yourapp", // to know which app sent it
+ "data":
+ {
+ “message_status":"MESSAGE_SENT_TO_DEVICE",
+ “original_message_id”:”m-1366082849205”
+ “device_registration_id”: “REGISTRATION_ID”
+ },
+ "message_id":"dr2:m-1366082849205",
+ "message_type":"receipt",
+ "from":"gcm.googleapis.com"
+ }
+ </gcm>
+</message></pre>
+
+<p>Note the following:</p>
+
+<ul>
+ <li>The {@code "message_type"} is set to {@code "receipt"}.
+ <li>The {@code "message_status"} is set to {@code "MESSAGE_SENT_TO_DEVICE"},
+ indicating that the message was delivered. Notice that in this case,
+{@code "message_status"} is not a field but rather part of the data payload.</li>
+ <li>The receipt message ID consists of the original message ID, but with a
+<code>dr:</code> prefix. Your 3rd-party app server must send an ACK back with this ID,
+which in this example is {@code dr2:m-1366082849205}.</li>
+ <li>The original message ID and status are inside the
+{@code "data"} field.</li>
+</ul>
+
<h2 id="flow">Flow Control</h2>
<p>Every message sent to CCS receives either an ACK or a NACK response. Messages
diff --git a/docs/html/google/gcm/client.jd b/docs/html/google/gcm/client.jd
index ac446dc..20bff10 100644
--- a/docs/html/google/gcm/client.jd
+++ b/docs/html/google/gcm/client.jd
@@ -246,7 +246,8 @@
<h3 id="sample-register">Register for GCM</h3>
<p>An Android application needs to register with GCM servers before it can receive
messages. When an app registers, it receives a registration ID, which it can then
-store for future use. In the following snippet the {@code onCreate()} method in the sample app's
+store for future use (note that registration IDs must be kept secret). In the
+following snippet the {@code onCreate()} method in the sample app's
main activity checks to see if the app is already registered with GCM and with
the server:</p>
diff --git a/docs/html/google/gcm/gcm.jd b/docs/html/google/gcm/gcm.jd
index 88bf659..19151b9 100644
--- a/docs/html/google/gcm/gcm.jd
+++ b/docs/html/google/gcm/gcm.jd
@@ -123,7 +123,7 @@
it to the 3rd-party application server, which uses it to identify each device
that has registered to receive messages for a given Android application. In other words,
a registration ID is tied to a particular Android application running on a particular
-device.
+device. Note that registration IDs must be kept secret.
<br/>
<br/>
<strong>Note:</strong> If you use
diff --git a/docs/html/google/gcm/index.jd b/docs/html/google/gcm/index.jd
index 70f7a9c..56e0865 100644
--- a/docs/html/google/gcm/index.jd
+++ b/docs/html/google/gcm/index.jd
@@ -14,7 +14,10 @@
<h1 itemprop="name" style="margin-bottom:0;">Google Cloud Messaging for Android</h1>
<p itemprop="description">
Google Cloud Messaging for Android (GCM) is a service that allows you to send data
-from your server to your users' Android-powered device, and also to receive messages from devices on the same connection. The GCM service handles all aspects of queueing of messages and delivery to the target Android application running on the target device. GCM is completely free no matter how big your messaging needs are, and there are no quotas.
+from your server to your users' Android-powered device, and also to receive messages from
+devices on the same connection. The GCM service handles all aspects of queueing of messages
+and delivery to the target Android application running on the target device. GCM is
+completely free no matter how big your messaging needs are, and there are no quotas.
</p>
</div>
@@ -27,31 +30,39 @@
<p>This could be a lightweight
message telling your app there is new data to be fetched from the
server (for instance, a movie uploaded by a friend), or it could be a message containing
-up to 4kb of payload data (so apps like instant messaging can consume the message directly). <a href="{@docRoot}google/gcm/gcm.html">GCM Architectural Overview.</a></p>
+up to 4kb of payload data (so apps like instant messaging can consume the message directly).
+<a href="{@docRoot}google/gcm/gcm.html">GCM Architectural Overview.</a></p>
<h4>Send "send-to-sync" messages</h4>
- <p>A send-to-sync (collapsible) message is often a "tickle" that tells a mobile application to sync data from the server. For example, suppose you have an email application. When a user receives new email on the server, the server pings the mobile application with a "New mail" message. This tells the application to sync to the server to pick up the new email.
- <a href="{@docRoot}google/gcm/adv.html#s2s">Send-to-sync messages</a>.</p>
- </a>
+ <p>A send-to-sync (collapsible) message is often a "tickle" that tells a mobile
+ application to sync data from the server. For example, suppose you have an email
+ application. When a user receives new email on the server, the server pings the mobile
+ application with a "New mail" message. This tells the application to sync to the server
+ to pick up the new email.
+ <a href="{@docRoot}google/gcm/adv.html#s2s">Learn more »</a></p>
<h4>Send messages with payload</h4>
- <p>Unlike a send-to-sync message, every "message with payload" (non-collapsible message) is delivered. The payload the message contains can be up to 4kb.
- <a href="{@docRoot}google/gcm/adv.html#payload">Messages with payload</a>.</p>
+ <p>Unlike a send-to-sync message, every "message with payload" (non-collapsible message)
+ is delivered. The payload the message contains can be up to 4kb.
+ <a href="{@docRoot}google/gcm/adv.html#payload">Learn more »</a></p>
</div>
<div class="col-6 normal-links">
<h3 style="clear:left">New Features</h3>
- <h4>Faster, easier GCM setup</h4>
- <p>Streamlined registration makes it simple and fast to add GCM support to your Android app. <a href="{@docRoot}google/gcm/gs.html">Learn more »</a></p>
- <h4>Upstream messaging over XMPP</h4>
- <p>GCM's Cloud Connection Service (CCS) lets you communicate with Android devices over a persistent XMPP connection. The primary advantages of CCS are speed, and the ability to receive upstream messages (that is, messages from a device to the cloud). You can use the service in tandem with existing GCM APIs. Use <a href="https://services.google.com/fb/forms/gcm/">this form</a> to sign up for CCS. <a href="{@docRoot}google/gcm/ccs.html">Learn more »</a></p>
- <h4>Seamless multi-device messaging</h4>
- <p>Maps a single user to a notification key, which you can then use to send a single message to multiple devices owned by the user. Use <a href="https://services.google.com/fb/forms/gcm/">this form</a> to sign up for User Notifications. <a href="{@docRoot}google/gcm/notifications.html">Learn more »</a></p>
+
+
+ <h4>Return Receipts</h4>
+ <p>You can use upstream messaging to get receipt notifications, confirming that a given
+ message was sent to a device. Your 3rd-party app server receives the receipt notification
+ from CCS once the message has been sent to the device.
+ <a href="{@docRoot}google/gcm/ccs.html#receipts">Learn more »</a></p>
+
<h4>Get Started</h4>
- <p>Get started using the new features with a tutorial that walks you through creating a GCM app. <a href="{@docRoot}google/gcm/gs.html">Learn more »</a></p>
+ <p>Get started with a tutorial that walks you through creating a GCM app.
+ <a href="{@docRoot}google/gcm/gs.html">Learn more »</a></p>
</div>
</div>
diff --git a/docs/html/google/gcm/notifications.jd b/docs/html/google/gcm/notifications.jd
index 43a7368..2815f3d 100644
--- a/docs/html/google/gcm/notifications.jd
+++ b/docs/html/google/gcm/notifications.jd
@@ -14,8 +14,8 @@
<h2>In this document</h2>
<ol class="toc">
- <li><a href="#request">Request Format</a></li>
- <li><a href="#create">Generate a Notification Key</a></li>
+ <li><a href="#gen-server">Generate a Notification Key on the Server</a></li>
+ <li><a href="#gen-client">Generate a Notification Key on the Client</a></li>
<li><a href="#add">Add Registration IDs</a></li>
<li><a href="#remove">Remove Registration IDs</a></li>
<li><a href="#upstream">Send Upstream Messages</a></li>
@@ -31,15 +31,11 @@
<ol class="toc">
<li><a href="{@docRoot}google/gcm/gs.html">Getting Started</a></li>
-<li><a href="https://services.google.com/fb/forms/gcm/" class="external-link" target="_android">CCS and User Notifications Signup Form</a></li>
</ol>
</div>
</div>
-<p class="note"><strong>Note:</strong> To try out this feature, sign up using <a href="https://services.google.com/fb/forms/gcm/">this form</a>.</p>
-
-
<p>With user notifications, 3rd-party app servers can send a single message to
multiple instance of an app running on devices owned by a single user. This feature
is called <em>user notifications</em>. User notifications make it possible for every
@@ -76,27 +72,23 @@
<p>You can use this feature with either the <a href="ccs.html">XMPP</a> (CCS) or
<a href="http.html">HTTP</a> connection server.</p>
+<p>You can generate notification keys in two different ways: on the server, and on
+the client, if the user has a Google account. All of the associated registration IDs
+can be mapped to a single user.</p>
<p>The examples below show you how to perform generate/add/remove operations,
and how to send upstream messages. For generate/add/remove operations, the
message body is JSON.</p>
-<h2 id="request">Request Format</h2>
-<p>To send a message, the application server issues a POST request to
-<code>https://android.googleapis.com/gcm/notification</code>.</p>
+<h2 id="gen-server">Generate a Notification Key on the Server</h2>
-<p>Here is the HTTP request header you should use for all create/add/remove operations:</p>
-
-<pre>content-type: "application/json"
-Header : "project_id": <projectID>
-Header: "Authorization", "key=API_KEY"
-</pre>
-
-<h2 id="create">Generate a Notification Key</h2>
+<p>To generate a notification key on the server, you create a new
+create a new <code>notification_key</code> and map it to a
+<code>notification_key_name</code>.</p>
<p>This example shows how to create a new <code>notification_key</code> for a
<code>notification_key_name</code> called <code>appUser-Chris</code>.
-The {@code notification_key_name} is a name or identifier (can be a username for
+The {@code notification_key_name} is a name or identifier (it can be a username for
a 3rd-party app) that is unique to a given user. It is used by third parties to
group together registration IDs for a single user. Note that <code>notification_key_name</code>
and <code>notification_key</code> are unique to a group of registration IDs. It is also
@@ -116,10 +108,109 @@
"registration_ids": ["4", "8", "15", "16", "23", "42"]
}</pre>
+<h3 id="request-server">Request format</h3>
+
+<p>To send a message in cases where your notification key is generated on the server,
+the application server issues a POST request to
+<code>https://android.googleapis.com/gcm/notification</code>.</p>
+
+<p>Here is the HTTP request header you should use for all server side create/add/remove operations:</p>
+
+<pre>content-type: "application/json"
+Header : "project_id": <projectID>
+Header: "Authorization", "key=API_KEY"
+</pre>
+
+
+<h2 id="gen-client">Generate a Notification Key on the Client</h2>
+
+<p>Generating a notification key on the client is useful for cases where a server is unavailable.
+To generate a notification key on the client, the device must have at least one
+Google account. Note that the process for generating a notification key on the client is significantly
+different from the server process described above.</p>
+
+<p>To generate a notification key on the client:</p>
+
+<ol>
+ <li>Open your project in the <a href="https://cloud.google.com/console">Google Developers Console</a>.</li>
+ <li>Click <strong>APIS & AUTH > Credentials</strong>.</li>
+ <li>Under OAuth, click <strong>Create new Client ID</strong>.</li>
+ <li>In the <strong>Create Client ID</strong> dialog, select <strong>Web Application</strong> as
+the application type, and click <strong>Create Client ID</strong>.</li>
+ <li>Copy the value from <strong>Client ID for web application > Client ID</strong>.
+This client ID represents a Google account "scope" that you will use to generate an {@code id_token}.</li>
+</ol>
+
+<p>Once you've followed the above steps and gotten a client ID from Google Developers Console,
+ you're ready to add this feature to your app. First check the device for the presence of a Google
+account. For example:</p>
+
+<pre>// This snippet takes the simple approach of using the first returned Google account,
+// but you can pick any Google account on the device.
+public String getAccount() {
+ Account[] accounts = AccountManager.get(getActivity()).
+ getAccountsByType("com.google");
+ if (accounts.length == 0) {
+ return null;
+ }
+ return accounts[0].name;
+}</pre>
+
+<p>Next, get an authentication token ({@code id_token}) by using the <code><a href=
+"http://developer.android.com/reference/com/google/android/gms/auth/GoogleAuthUtil.html">GoogleAuthUtil</a></code>
+class. For example:</p>
+
+<pre>String accountName = getAccount();
+
+// Initialize the scope using the client ID you got from the Console.
+final String scope = "audience:server:client_id:"
+ + "1262xxx48712-9qs6n32447mcj9dirtnkyrejt82saa52.apps.googleusercontent.com";
+String id_token = null;
+try {
+ id_token = GoogleAuthUtil.getToken(context, accountName, scope);
+} catch (Exception e) {
+ log("exception while getting id_token: " + e);
+}
+...</pre>
+
+<p>Now use <code>id_token</code> to authenticate your request.
+This add operation returns a {@code notification_key}.
+Third parties must save this {@code notification_key} (as well as its mapping to the
+<code>notification_key_name</code>)
+to use in subsequent operations. Note that a client request only takes a single regID.
+The only operations supported on the client side are add/remove.</p>
+
+<pre>request:
+{
+ "operation": "add",
+ "notification_key_name": "appUser-Chris",
+ "registration_ids": ["4"]
+ "id_token": "id_token"
+}</pre>
+
+<h3 id="request-client">Request format</h3>
+
+<p>To send a message in cases where your notification key is generated on the client,
+the application server issues a POST request to
+<code>https://android.googleapis.com/gcm/googlenotification</code>.</p>
+
+<p>Here is the HTTP request header you should use for all add/remove operations. The
+client side doesn't support the create operation;
+the add operation has the effect of creating the notification key if it doesn't already
+exist:</p>
+
+<pre>content-type: "application/json"
+Header : "project_id": <projectID>
+</pre>
+
+<p>Note that the authentication token is passed in the JSON body as shown above, not the header.
+This is different from the server case.</p>
+
+
<h2 id="add">Add Registration IDs</h2>
<p>This example shows how to add registration IDs for a given notification key.
-The maximum number of members allowed for a {@code notification_key} is 10.</p>
+The maximum number of members allowed for a {@code notification_key} is 20.</p>
<p>Note that the <code>notification_key_name</code> is not strictly required for
adding/removing regIDs. But including it protects you against accidentally using
diff --git a/docs/html/google/gcm/server.jd b/docs/html/google/gcm/server.jd
index ccd1267..e3a6b25 100644
--- a/docs/html/google/gcm/server.jd
+++ b/docs/html/google/gcm/server.jd
@@ -120,7 +120,8 @@
<li>Able to store the API key and client registration IDs. The
API key is included in the header of POST requests that send
messages.</li>
- <li>Able to generate message IDs to uniquely identify each message it sends.</li>
+ <li>Able to generate message IDs to uniquely identify each message it sends. Message IDs
+should be unique per sender ID.</li>
</ul>
<h2 id="send-msg">Sending Messages</h2>
diff --git a/docs/html/google/gcs/gcs-signup.jd b/docs/html/google/gcs/gcs-signup.jd
new file mode 100644
index 0000000..7334cec
--- /dev/null
+++ b/docs/html/google/gcs/gcs-signup.jd
@@ -0,0 +1,10 @@
+page.title=Sign Up for Google Cloud Save
+
+@jd:body
+
+<p>Sign up to be a trial partner for Google Cloud Save.</p>
+
+
+<iframe src="https://docs.google.com/a/google.com/forms/d/1_V67YIXzLDLb-UzxOOpSjUDuJFfeYg3hEUT0oliK2ck/viewform?embedded=true" width="100%" height="930" frameborder="0" marginheight="0" marginwidth="0" id="signupform">Loading...</iframe>
+</body>
+</html>
diff --git a/docs/html/google/gcs/index.jd b/docs/html/google/gcs/index.jd
new file mode 100644
index 0000000..e5f4776
--- /dev/null
+++ b/docs/html/google/gcs/index.jd
@@ -0,0 +1,30 @@
+page.title=Google Cloud Save
+page.tags="gcs"
+header.hide=1
+@jd:body
+
+
+<div class="landing-banner">
+
+<div class="col-5" style="min-height:100px">
+ <img src="{@docRoot}images/google/gcs.png" />
+</div>
+<div class="col-7">
+
+ <h1 itemprop="name" style="margin-bottom:0;">Google Cloud Save</h1>
+ <p itemprop="description">
+ Google Cloud Save is a service that enables per-user data storage
+and sync in your apps with no backend programming required. Google Cloud Save
+stores its data
+in <a href="http://developers.google.com/datastore/">Google Cloud Datastore</a>,
+ a fully managed, schemaless database for storing non-relational data. Cloud
+Datastore automatically scales with your users.
+Google Cloud Save works even when your device is offline, and it
+provides an easy transition to server-side coding because
+the same database is accessible via App Engine and Compute Engine.
+Finally, Google Cloud Save provides a generous initial per-user free quota that
+expands as your user base grows.
+</p>
+<a href="{@docRoot}google/gcs/gcs-signup.html" class="button">Sign Up</a>
+</div>
+</div>
diff --git a/docs/html/google/index.jd b/docs/html/google/index.jd
index 2e97d62..4778a85 100644
--- a/docs/html/google/index.jd
+++ b/docs/html/google/index.jd
@@ -89,6 +89,16 @@
to use Google Cloud Messaging.</p>
</div>
+<div class="landing-cell">
+ <div class="cell-icon">
+ <img src="{@docRoot}images/google/gcs-small.png" width="40" >
+ </div>
+ <h4><a href="{@docRoot}google/gcs/index.html"
+ >Google Cloud Save</a></h4>
+ <p>Enable per-user data storage and sync in your apps with no backend programming
+ required.</p>
+</div>
+
</div><!-- col-6 -->
diff --git a/docs/html/guide/practices/verifying-apps-art.jd b/docs/html/guide/practices/verifying-apps-art.jd
index 0eedfaf..8a88222 100644
--- a/docs/html/guide/practices/verifying-apps-art.jd
+++ b/docs/html/guide/practices/verifying-apps-art.jd
@@ -66,7 +66,7 @@
href="{@docRoot}/tools/debugging/debugging-memory.html#LogMessages"><code>GC_FOR_ALLOC</code></a>-type
occurrences or to reduce fragmentation. You can verify which runtime is in use
by calling {@link java.lang.System#getProperty(java.lang.String)
-System.getProperty("dalvik.vm.version")}. If ART is in use, the property's value
+System.getProperty("java.vm.version")}. If ART is in use, the property's value
is <code>"2.0.0"</code> or higher.</p>
<p>Furthermore, a compacting garbage collector is under development in the <a
diff --git a/docs/html/images/google/datastore-logo.png b/docs/html/images/google/datastore-logo.png
new file mode 100644
index 0000000..a0fc0a0
--- /dev/null
+++ b/docs/html/images/google/datastore-logo.png
Binary files differ
diff --git a/docs/html/images/google/gcs-small.png b/docs/html/images/google/gcs-small.png
new file mode 100644
index 0000000..b8dbe0d
--- /dev/null
+++ b/docs/html/images/google/gcs-small.png
Binary files differ
diff --git a/docs/html/images/google/gcs.png b/docs/html/images/google/gcs.png
new file mode 100644
index 0000000..7355d64
--- /dev/null
+++ b/docs/html/images/google/gcs.png
Binary files differ
diff --git a/docs/html/images/home/auto-wordmark.png b/docs/html/images/home/auto-wordmark.png
new file mode 100644
index 0000000..027dfca
--- /dev/null
+++ b/docs/html/images/home/auto-wordmark.png
Binary files differ
diff --git a/docs/html/images/home/auto.png b/docs/html/images/home/auto.png
index 233d69d..4ce882a 100644
--- a/docs/html/images/home/auto.png
+++ b/docs/html/images/home/auto.png
Binary files differ
diff --git a/docs/html/images/home/tv-wordmark.png b/docs/html/images/home/tv-wordmark.png
new file mode 100644
index 0000000..e7660ac
--- /dev/null
+++ b/docs/html/images/home/tv-wordmark.png
Binary files differ
diff --git a/docs/html/images/home/tv.png b/docs/html/images/home/tv.png
index 47bf4b0..7208a99 100644
--- a/docs/html/images/home/tv.png
+++ b/docs/html/images/home/tv.png
Binary files differ
diff --git a/docs/html/images/home/wear-wordmark.png b/docs/html/images/home/wear-wordmark.png
new file mode 100644
index 0000000..e645887
--- /dev/null
+++ b/docs/html/images/home/wear-wordmark.png
Binary files differ
diff --git a/docs/html/images/home/wear.png b/docs/html/images/home/wear.png
index c7a2045..e6602fd 100644
--- a/docs/html/images/home/wear.png
+++ b/docs/html/images/home/wear.png
Binary files differ
diff --git a/docs/html/images/material.png b/docs/html/images/material.png
new file mode 100644
index 0000000..981359b
--- /dev/null
+++ b/docs/html/images/material.png
Binary files differ
diff --git a/docs/html/images/resource-card-android-studio.png b/docs/html/images/resource-card-android-studio.png
new file mode 100644
index 0000000..0f483df
--- /dev/null
+++ b/docs/html/images/resource-card-android-studio.png
Binary files differ
diff --git a/docs/html/images/tools/wizard2.png b/docs/html/images/tools/wizard2.png
new file mode 100644
index 0000000..921d58c
--- /dev/null
+++ b/docs/html/images/tools/wizard2.png
Binary files differ
diff --git a/docs/html/images/tools/wizard3.png b/docs/html/images/tools/wizard3.png
new file mode 100644
index 0000000..d5749d5
--- /dev/null
+++ b/docs/html/images/tools/wizard3.png
Binary files differ
diff --git a/docs/html/images/tools/wizard4.png b/docs/html/images/tools/wizard4.png
new file mode 100644
index 0000000..2709f7b
--- /dev/null
+++ b/docs/html/images/tools/wizard4.png
Binary files differ
diff --git a/docs/html/images/tools/wizard5.png b/docs/html/images/tools/wizard5.png
new file mode 100644
index 0000000..105cf2a
--- /dev/null
+++ b/docs/html/images/tools/wizard5.png
Binary files differ
diff --git a/docs/html/images/tools/wizard6.png b/docs/html/images/tools/wizard6.png
new file mode 100644
index 0000000..8b0691c
--- /dev/null
+++ b/docs/html/images/tools/wizard6.png
Binary files differ
diff --git a/docs/html/images/tools/wizard7.png b/docs/html/images/tools/wizard7.png
new file mode 100644
index 0000000..83396cf
--- /dev/null
+++ b/docs/html/images/tools/wizard7.png
Binary files differ
diff --git a/docs/html/index.jd b/docs/html/index.jd
index c2bf6b6..253a7a5 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -6,7 +6,6 @@
@jd:body
-<!-- Top full-bleed carousel -->
<div class="home-new-carousel-1">
<div class="fullscreen-carousel-content">
<div class="vcenter">
@@ -14,35 +13,30 @@
<div class="static resource-flow-layout wrap col-16">
<div class="resource resource-card resource-card-18x6">
- <div class="card-bg" style="background-image: url('/preview/images/l-dev-prev.png');"></div>
- <div class="card-info">
- <div class="section"></div>
- <div class="title">Android L Developer Preview</div>
- <div class="description ellipsis" style="height: 285px;">
- <div class="text" style="height: auto;">
- <p style="font-size:16px;">Get an early look at the next Android release and
- start using new APIs so your apps are ready when the platform officially launches.</p>
- <p>
- <a href="{@docRoot}preview/index.html" class="landing-button landing-secondary">Learn more</a></p>
- </div>
- </div>
+
+ <div class="landing-section-header">
+ <div class="col-10"><img src="{@docRoot}preview/images/l-dev-prev.png"
+ style="margin:40px 60px 0 20px"></div>
+ <div class="col-5" style=" margin-top:70px ">
+ <h3 stye="font-weight:300;">L Developer Preview</h3>
+ <p>The L Developer Preview lets you design and develop against the next major
+ release of Android. Take the time to test and build your app before the platform
+ officially launches. </p>
+ <a href="{@docRoot}preview/index.html" class="landing-button landing-primary">Learn More</a>
</div>
</div>
+ </div>
</div>
-
- <div class="resource-widget resource-flow-layout wrap col-16 no-section"
- data-query="collection:index/primary"
- data-resourceStyle="card"
- data-sortOrder="-timestamp"
- data-maxResults="3"
- data-cardSizes="6x2,6x2,6x2">
- </div> <!-- end .resource-widget -->
+ <h2> </h2>
+ <div style="margin-top:20px" class="resource-widget resource-flow-layout wrap col-16
+ no-section" data-query="collection:index/primary" data-resourcestyle="card"
+ data-sortorder="-timestamp" data-maxresults="3" data-cardsizes="6x2,6x2,6x2"></div> <!-- end .resource-widget -->
</div> <!-- end .wrap -->
</div> <!-- end .vcenter -->
</div> <!-- end .fullscreen-carousel-content -->
</div> <!-- end .fullscreen-carousel -->
-<div class="actions-bar">
+<div class="actions-bar" style="margin-top:20px">
<div class="wrap">
<div class="actions">
<div><a href="{@docRoot}sdk/index.html">Get the SDK</a></div>
@@ -59,22 +53,46 @@
<div class="landing-section">
<div class="wrap">
<div class="landing-section-header">
- <div class="landing-h1">Develop for Multiple Form Factors</div>
+
+ <div class="landing-h1" style="margin-top:0px">Build for a Multi-Screen World</div>
<div class="landing-subhead" style="margin-top: 20px;">
- Android runs on hundreds of millions of handheld devices around the world, <br />
+ Android runs on hundreds of millions of handheld devices around the world, <br>
and it now supports these exciting, new form-factors.
</div>
</div>
- <div class="landing-body" style="margin-top: 80px;">
+ <div class="landing-body" style="margin-top: 50px;">
<div class="landing-breakout cols">
<div class="col-3-wide">
- <a href="{@docRoot}wear/index.html"><img src="{@docRoot}images/home/wear.png"></a>
+ <img src="{@docRoot}images/home/wear-wordmark.png">
+ <img src="{@docRoot}images/home/wear.png">
+ <p class="landing-small">
+ Provide information on-the-go for your users, whenever they need it.
+ </p>
+ <p class="landing-small">
+ <a href="{@docRoot}wear/index.html">Learn about Android Wear</a>
+ </p>
</div>
- <div class="col-3-wide">
- <a href="{@docRoot}tv/index.html"><img src="{@docRoot}images/home/tv.png"></a>
+ <div class="col-3-wide">
+ <img src="{@docRoot}images/home/tv-wordmark.png">
+ <img src="{@docRoot}images/home/tv.png">
+ <p class="landing-small">
+ Build your apps for the big screen and bring your content to life.
+ </p>
+ <p class="landing-small">
+ <a href="{@docRoot}tv/index.html">Learn about Android TV</a>
+
+ </p>
</div>
- <div class="col-3-wide">
- <a href="{@docRoot}auto/index.html"><img src="{@docRoot}images/home/auto.png"></a>
+ <div class="col-3-wide">
+ <img src="{@docRoot}images/home/auto-wordmark.png">
+ <img src="{@docRoot}images/home/auto.png">
+ <p class="landing-small">
+ Extend your music apps to automobile
+ entertainment systems.
+ </p>
+ <p class="landing-small">
+ <a href="{@docRoot}auto/index.html">Learn about Android Auto</a>
+ </p>
</div>
</div>
</div>
diff --git a/docs/html/jd_collections.js b/docs/html/jd_collections.js
index a92236e..cd49ace 100644
--- a/docs/html/jd_collections.js
+++ b/docs/html/jd_collections.js
@@ -2,9 +2,9 @@
"index/primary": {
"title": "",
"resources": [
- "distribute/essentials/quality/tablets.html",
- "distribute/engage/game-services.html",
- "distribute/googleplay/edu/about.html"
+ "training/building-wearables.html",
+ "preview/material/index.html",
+ "sdk/installing/studio.html"
]
},
"index/devices": {
diff --git a/docs/html/jd_extras.js b/docs/html/jd_extras.js
index eb7bfb6..605af52 100644
--- a/docs/html/jd_extras.js
+++ b/docs/html/jd_extras.js
@@ -15,17 +15,6 @@
DISTRIBUTE_RESOURCES = DISTRIBUTE_RESOURCES.concat([
{
- "title":"Android L Developer Preview",
- "titleFriendly":"",
- "summary":"<p style='font-size:18px;'>Get an early look at the next release and get your apps ready when the platform officially launches.</p>",
- "url":"preview/index.html",
- "group":"",
- "keywords": [],
- "tags": [],
- "image":"preview/images/l-dev-prev.png",
- "type":""
- },
- {
"title":"Developer Registration",
"titleFriendly":"",
"summary":"Additional information about the registration process.",
@@ -1172,7 +1161,7 @@
"tags": [],
"url": "https://developers.google.com/edu/",
"timestamp": null,
- "image": "https://developers.google.com/edu/images/edu-guidelines.jpg",
+ "image": "https://developers.google.com/edu/images/home-android.png",
"title": "Chrome Apps in Google Play for Education",
"summary": "Find out more about Chrome apps in Google Play for Education.",
"keywords": [],
@@ -1192,4 +1181,4 @@
"type": "",
"titleFriendly": ""
}
-]);
\ No newline at end of file
+]);
diff --git a/docs/html/jd_tag_helpers.js b/docs/html/jd_tag_helpers.js
index b0fe67a..7538e4d 100644
--- a/docs/html/jd_tag_helpers.js
+++ b/docs/html/jd_tag_helpers.js
@@ -64,6 +64,7 @@
// Type lookups
var ALL_RESOURCES_BY_TYPE = {
+ 'about': ABOUT_RESOURCES,
'design': DESIGN_RESOURCES,
'distribute': DISTRIBUTE_RESOURCES,
'google': GOOGLE_RESOURCES,
@@ -79,6 +80,7 @@
// Tag lookups
var ALL_RESOURCES_BY_TAG = mergeMaps(
+ {map:ABOUT_BY_TAG,arr:ABOUT_RESOURCES},
{map:DESIGN_BY_TAG,arr:DESIGN_RESOURCES},
{map:DISTRIBUTE_BY_TAG,arr:DISTRIBUTE_RESOURCES},
{map:GOOGLE_BY_TAG,arr:GOOGLE_RESOURCES},
diff --git a/docs/html/preview/api-overview.jd b/docs/html/preview/api-overview.jd
index d9cac48..2e99194 100644
--- a/docs/html/preview/api-overview.jd
+++ b/docs/html/preview/api-overview.jd
@@ -24,26 +24,21 @@
<li><a href="#UI">User Interface</a>
<ol>
<li><a href="#MaterialDesign">Material design support</a></li>
- <li><a href="#DoNotDisturb">Do Not Disturb mode</a></li>
<li><a href="#LockscreenNotifications">Lockscreen notifications</a></li>
<li><a href="#NotificationsMetadata">Notifications metadata</a></li>
<li><a href="#Recents">Concurrent documents and activities in the Recents screen</a></li>
<li><a href="#WebView">WebView updates</a></li>
</ol>
</li>
- <li><a href="#UserInput">User Input</a>
- <ol>
- <li><a href="#IME">IME bug fixes and improvements</a></li>
- </ol>
- </li>
- <li><a href="#Animations">Animation & Graphics</a>
+ <li><a href="#Graphics">Graphics</a>
<ol>
<li><a href="#OpenGLES-3-1">Support for OpenGL ES 3.1</a></li>
+ <li><a href="#AndroidExtensionPack">Android Extension Pack</a></li>
</ol>
</li>
<li><a href="#Multimedia">Multimedia</a>
<ol>
- <li><a href="#Camera-v2">Camera v2 API</a></li>
+ <li><a href="#Camera-v2">Camera API for advanced camera capabilities</a></li>
<li><a href="#AudioPlayback">Audio playback</a></li>
<li><a href="#MediaPlaybackControl">Media playback control</a></li>
</ol>
@@ -55,7 +50,7 @@
</li>
<li><a href="#Wireless">Wireless and Connectivity</a>
<ol>
- <li><a href="#Multinetwork">Dynamic network selection and seamless handoff</a></li>
+ <li><a href="#Multinetwork">Multiple network connections</a></li>
<li><a href="#BluetoothBroadcasting">Bluetooth broadcasting</a></li>
<li><a href="#NFCEnhancements">NFC enhancements</a></li>
</ol>
@@ -82,20 +77,18 @@
<li><a href="#TestingA11yImprovements">Testing and accessibility improvements</a></li>
</ol>
</li>
+ <li><a href="#IME">IME</a>
+ <ol>
+ <li><a href="#Switching">Easier switching between input languages</a></li>
+ </ol>
+ </li>
<li><a href="#Manifest">Manifest Declarations</a>
<ol>
<li><a href="#ManifestFeatures">Declarable required features</a></li>
- <li><a href="#ManifestPermissions">User permissions</a></li>
</ol>
</li>
</ol>
-<h2>See also</h2>
-<ol>
-<li><a href="{@docRoot}sdk/api_diff/20/changes.html">API
-Differences Report »</a> </li>
-</ol>
-
</div>
</div>
@@ -117,7 +110,7 @@
href="{@docRoot}">developer.android.com</a>. These API elements are
formatted in {@code code style} in this document (without hyperlinks). For the
preliminary API documentation for these elements, download the <a
-href="{@docRoot}preview/l-developer-preview-reference.zip">preview
+href="http://storage.googleapis.com/androiddevelopers/preview/l-developer-preview-reference.zip">preview
reference</a>.</p>
<h2 id="Behaviors">Important Behavior Changes</h2>
@@ -188,8 +181,8 @@
<p>If you are currently adding sounds and vibrations to your notifications by
using the {@link android.media.Ringtone}, {@link android.media.MediaPlayer},
or {@link android.os.Vibrator} classes, remove this code so that
-the system can present notifications correctly in <a href="#DoNotDisturb">Do
-Not Disturb</a> mode. Instead, use the {@link android.app.Notification.Builder}
+the system can present notifications correctly in Do
+not Disturb mode. Instead, use the {@link android.app.Notification.Builder}
methods instead to add sounds and vibration.</p>
<p>Notifications now appear in a small floating window
@@ -302,43 +295,16 @@
notification object, attach the replacement notification to it through the
{@code Notification.Builder.setPublicVersion()} method.</p>
-<h3 id="DoNotDisturb">Do Not Disturb mode</h3>
-
-<p>The L Developer Preview introduces a new <em>Do Not Disturb</em> mode. When
-the user puts the device in <em>Do Not Disturb</em> mode, the device limits
-the frequency of the notifications it shows the user (when the user
-wants to avoid distractions). The user can
-customize the feature in a number of ways, such as:</p>
-
-<ul>
- <li>Specifying important people, whose calls should go through even when
- the device is in <em>Do Not Disturb</em> mode.</li>
- <li>Setting custom categories to allow notifications when the device is in
- <em>Do Not Disturb</em> mode. Examples of such categories include phone
- calls and direct communications (like Hangouts and Skype calls).</li>
- <li>Setting rules so <em>Do Not Disturb</em> automatically goes into effect in
- certain conditions (like at particular times of day).</li>
-</ul>
-
-<p>You should add the appropriate metadata to your app notifications to help
-make sure <em>Do Not Disturb</em> mode handles them properly. For example, if
-your app is an alarm clock,
-you can tag the notification as an alarm so it will wake the user up even if the
-device is in <em>Do Not Disturb</em> mode. For more information, see <a
-href="#NotificationsMetadata">Notifications metadata</a>.</p>
-
<h3 id="NotificationsMetadata">Notifications metadata</h3>
<p>The L Developer Preview uses metadata associated with your app notifications
-to sort the notifications more intelligently. The metadata you set also
-controls how the system presents your app notifications when the user is in <em>Do
-Not Disturb</em> mode. To set the metadata, call the following methods in
-{@code android.app.Notification.Builder} when you construct the
-notification:</p>
+to sort the notifications more intelligently. To set the metadata, call the
+following methods in {@code android.app.Notification.Builder} when you
+construct the notification:</p>
<ul>
<li>{@code setCategory()}. Depending on the message category, this tells
the system how to handle your app notifications when the device is
-in <em>Do Not Disturb</em> mode (for example, if your notification represents an
+in <em>Do not Disturb</em> mode (for example, if your notification represents an
incoming call, instant message, or alarm).
<li>{@code setPriority()}. Notifications with the priority field set to
{@code PRIORITY_MAX} or {@code PRIORITY_HIGH} will appear in a small floating
@@ -354,11 +320,11 @@
<p>In previous releases, the
<a href="{@docRoot}design/get-started/ui-overview.html">Recents screen</a>
could only display a single task for each app that the user interacted with
-most recently. The L Developer Preview enables your app to open more tasks as
+most recently. Now your app can open more tasks as
needed for additional concurrent activities for documents.
-This feature facilitates multitasking
-by letting users quickly switch between individual activities and documents
-from the Recents screen, with a consistent switching experience across all apps.
+This feature facilitates multitasking by letting users quickly switch between
+individual activities and documents from the Recents screen, with a consistent
+switching experience across all apps.
Examples of such concurrent tasks might include open tabs in a web
browser app, documents in a productivity app, concurrent matches in
a game, or chats in a messaging app. Your app can manage its tasks
@@ -394,35 +360,12 @@
been updated to incorporate 36.0.0.0 as the version number.</p>
<p>Additionally, this release brings support for the
-<a href="https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html">WebAudio</a>,
-<a href="https://www.khronos.org/webgl/">WebGL</a>, and
-<a href="http://www.webrtc.org/">WebRTC</a> open standards. To learn more about
-the new features included in this release, see <a href="https://developer.chrome.com/multidevice/webview/overview">WebView for Android</a>.</p>
+<a href="http://webaudio.github.io/web-audio-api/" class="external-link">WebAudio</a>,
+<a href="https://www.khronos.org/webgl/" class="external-link">WebGL</a>, and
+<a href="http://www.webrtc.org/" class="external-link">WebRTC</a> open standards. To learn more about
+the new features included in this release, see <a href="https://developer.chrome.com/multidevice/webview/overview" class="external-link">WebView for Android</a>.</p>
-<h2 id="UserInput">User Input</h2>
-
-<h3 id="IME">IME bug fixes and improvements</h3>
-
-<p>Beginning in the L Developer Preview, users can more easily switch between
-all <a href="{@docRoot}guide/topics/text/creating-input-method.html">input
-method editors (IME)</a> supported by the platform. Performing the designated
-switching action (usually touching a Globe icon on the soft keyboard) will cycle
-among all such IMEs. This change takes place in
-{@link android.view.inputmethod.InputMethodManager#shouldOfferSwitchingToNextInputMethod
-InputMethodManager.shouldOfferSwitchingToNextInputMethod()}.</p>
-
-<p>In addition, the framework now checks whether the next IME includes a
-switching mechanism at all (and, thus, whether that IME supports switching to
-the IME after it). An
-IME with a switching mechanism will not cycle to an IME without one. This
-change takes place in
-{@link android.view.inputmethod.InputMethodManager#switchToNextInputMethod
-InputMethodManager.switchToNextInputMethod}.
-
-<p>To see an example of how to use the updated IME-switching APIs, refer to the
-updated soft-keyboard implementation sample in this release.</p>
-
-<h2 id="Animations">Animation & Graphics</h2>
+<h2 id="Graphics">Graphics</h2>
<h3 id="OpenGLES-3-1">Support for OpenGL ES 3.1</h3>
<p>The L Developer Preview adds Java interfaces and native support for OpenGL
@@ -432,13 +375,13 @@
<li>Compute shaders
<li>Separate shader objects
<li>Indirect draw commands
-<li>Enhanced texturing functionality
+<li>Multisample and stencil textures
<li>Shading language improvements
-<li>Optional extensions for per-sample shading, advanced blending modes, and more
+<li>Extensions for advanced blend modes and debugging
<li>Backward compatibility with OpenGL ES 2.0 and 3.0
</ul>
-<p>The Java interface for OpenGL ES 3.1 on Android is provided with GLES31. When
+<p>The Java interface for OpenGL ES 3.1 on Android is provided with {@code GLES31}. When
using OpenGL ES 3.1, be sure that you declare it in your manifest file with the
<a href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code <uses-feature>}</a>
tag and the {@code android:glEsVversion} attribute. For example:</p>
@@ -454,9 +397,32 @@
device’s supported OpenGL ES version at runtime, see the
<a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL ES API guide</a>.</p>
+<h3 id="AndroidExtensionPack">Android Extension Pack</h3>
+
+<p>In addition to OpenGL ES 3.1, this release provides an extension pack with Java interfaces and
+native support for advanced graphics functionality. These extensions are treated as a single
+package by Android. (If the {@code ANDROID_extension_pack_es31} extension is present, your app can
+assume all extensions in the package are present and enable the shading language features with
+a single {@code #extension} statement.</p>
+<p>The extension pack supports:</p>
+<ul>
+<li>Guaranteed fragment shader support for shader storage buffers, images, and
+ atomics (fragment shader support is optional in OpenGL ES 3.1.)</li>
+<li>Tessellation and geometry shaders</li>
+<li>ASTC (LDR) texture compression format</li>
+<li>Per-sample interpolation and shading</li>
+<li>Different blend modes for each color attachment in a frame buffer</li>
+</ul>
+
+<p>The Java interface for the extension pack is provided with {@code GLES31Ext}.
+In your app manifest, you can declare that support for the extension pack is
+required, with the
+<a href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code <uses-feature>}</a>
+tag, but the precise syntax is not finalized in the L Developer Preview.</p>
+
<h2 id="Multimedia">Multimedia</h2>
-<h3 id="Camera-v2">Camera v2 API</h3>
+<h3 id="Camera-v2">Camera API for advanced camera capabilities</h3>
<p>The L Developer Preview introduces the new {@code android.hardware.camera2}
API to facilitate fine-grain photo capture and image processing. You can now
@@ -474,6 +440,9 @@
{@code onCaptureCompleted()}, providing you with the image capture metadata in a
{@code CaptureResult}.</p>
+<p>To see an example of how to use the updated Camera API, refer to the {@code Camera2Basic}
+and {@code Camera2Video} implementation samples in this release.</p>
+
<h3 id="AudioPlayback">Audio playback</h3>
<p>This release includes the following changes to
{@link android.media.AudioTrack}:</p>
@@ -557,7 +526,7 @@
<h2 id="Wireless">Wireless & Connectivity</h2>
-<h3 id="Multinetwork">Dynamic network selection and seamless handoff</h3>
+<h3 id="Multinetwork">Multiple network connections</h3>
<p>The L Developer Preview provides new multi-networking APIs. These let your app
dynamically scan for available networks with specific capabilities, and
establish a connection to them. This is useful when your app requires a
@@ -680,6 +649,9 @@
jobScheduler.schedule(uploadTask);
</pre>
+<p>To see an example of how to use the {@code JobScheduler} API, refer to the
+{@code JobSchedulerSample} implementation sample in this release.</p>
+
<h3 id="PowerMeasurementTools">Developer tools for power measurement</h3>
<p>The L Developer Preview provides several new developer tools and APIs to help
you better measure and understand your app's power usage.</p>
@@ -762,31 +734,27 @@
</div>
<p>The L Developer Preview provides new functionality for running apps within
-an enterprise environment. A device administrator can
-initiate a managed provisioning process to add a co-present but separate <em>managed
-profile</em> to a device with an existing personal account. The administrator has
-control over the managed profile.</p>
+an enterprise environment. A
+<a href="{@docRoot}guide/topics/admin/device-admin.html">device administrator</a> can
+initiate a managed provisioning process to add a co-present but separate <em>managed profile</em> to a device, if the user has an existing personal account.
+Apps that are associated with managed profiles will appear alongside
+non-managed apps in the user’s Launcher, Recent apps screen, and notifications.</p>
<p>To start the managed provisioning process, send {@code
ACTION_PROVISION_MANAGED_PROFILE} in an {@link android.content.Intent}. If the
call is successful, the system triggers the {@code
android.app.admin.DeviceAdminReceiver. onProfileProvisioningComplete()} callback.
You can then call {@code app.admin.DevicePolicyManager. setProfileEnabled()} to
-set this profile to the enabled state.</p>
-
-<p>A user may be associated with more than one managed profile. To get a list of
-the managed profiles associated with the user, call
-{@code android.os.UserManager. getUserProfiles()}.</p>
-
-<p>Once a managed profile is created for a user, apps that are managed by the
-device administrator will appear alongside non-managed apps in the user’s
-Launcher, Recent apps screen, and notifications.</p>
+enable this managed profile.</p>
<p>If you are developing a Launcher app, you can use the new {@code
android.content.pm.LauncherApps} class to get a list of launchable activities
for the current user and any associated managed profiles. Your Launcher can make
the managed apps visually prominent by appending a “work” badge to the icon
-drawable with {@code android.os.UserManager.getBadgeDrawableForUser()}.</p>
+drawable with {@code android.os.UserManager. getBadgeDrawableForUser()}.</p>
+
+<p>To see an example of how to use the new functionality, refer to the
+{@code BasicManagedProfile} implementation sample in this release.</p>
<h3 id="TaskLocking">Task locking</h3>
<p>The L Developer Preview introduces a new task locking API that
@@ -803,7 +771,7 @@
<p>To set up a device owner, follow these steps:</p>
<ol>
-<li>Attach a device running an <a href="https://source.android.com/source/building-running.html">Android {@code userdebug} build</a> to your development machine.</li>
+<li>Attach a device running an Android <a href="https://source.android.com/source/building-running.html" class="external-link">{@code userdebug}</a> build to your development machine.</li>
<li>Install your device owner app.</li>
<li>Create a {@code device_owner.xml} file and save it to the {@code /data/system}
directory on the device.
@@ -851,7 +819,7 @@
{@code render()} to turn the opened {@code PdfRenderer.Page} into a bitmap. You
can also set additional parameters if you only want to convert a portion of the
document into a bitmap image (for example, to implement
-<a href="http://en.wikipedia.org/wiki/Tiled_rendering">tiled rendering</a> in
+<a href="http://en.wikipedia.org/wiki/Tiled_rendering" class="external-link">tiled rendering</a> in
order to zoom in on the document).</p>
<h2 id="TestingA11y">Testing & Accessibility </h2>
@@ -886,6 +854,29 @@
previously found in {@code AccessibilityNodeInfo}.
</ul>
+<h2 id="IME">IME</h2>
+
+<h3 id="Switching">Easier switching between input languages</h3>
+
+<p>Beginning in the L Developer Preview, users can more easily switch between
+all <a href="{@docRoot}guide/topics/text/creating-input-method.html">input
+method editors (IME)</a> supported by the platform. Performing the designated
+switching action (usually touching a Globe icon on the soft keyboard) will cycle
+among all such IMEs. This change takes place in
+{@link android.view.inputmethod.InputMethodManager#shouldOfferSwitchingToNextInputMethod
+InputMethodManager.shouldOfferSwitchingToNextInputMethod()}.</p>
+
+<p>In addition, the framework now checks whether the next IME includes a
+switching mechanism at all (and, thus, whether that IME supports switching to
+the IME after it). An
+IME with a switching mechanism will not cycle to an IME without one. This
+change takes place in
+{@link android.view.inputmethod.InputMethodManager#switchToNextInputMethod
+InputMethodManager.switchToNextInputMethod}.
+
+<p>To see an example of how to use the updated IME-switching APIs, refer to the
+updated soft-keyboard implementation sample in this release.</p>
+
<h2 id="Manifest">Manifest Declarations</h2>
<h3 id="ManifestFeatures">Declarable required features</h3>
@@ -897,7 +888,7 @@
<ul>
<li>{@code FEATURE_LEANBACK}. Declares that your app must be installed only on
devices that support the
-<a href="{@docRoot}training/tv/index.html}">Android TV</a> user interface.
+<a href="{@docRoot}training/tv/index.html">Android TV</a> user interface.
Example:
<pre>
<uses-feature android:name="android.software.leanback"
@@ -912,13 +903,5 @@
</pre>
</ul>
-<h3 id="ManifestPermissions">User permissions</h3>
-<p>The following values are now supported in the
-<a href="{@docRoot}guide/topics/manifest/uses-permission-element.html">{@code
- <uses-permission>}</a> to declare the
-permissions your app requires in order to access certain APIs.
-
-<ul>
-<li>{@code SIM_COMMUNICATION}. Required to communicate with a SIM card using
- logical channels.
-</ul>
+<p class="note">For a detailed view of all API changes in the L Developer Preview, see the
+<a href="{@docRoot}preview/reference.html">API Differences Report</a>.</p>
diff --git a/docs/html/preview/google-play-services-wear.html b/docs/html/preview/google-play-services-wear.html
index ad8891f..84647b7 100644
--- a/docs/html/preview/google-play-services-wear.html
+++ b/docs/html/preview/google-play-services-wear.html
@@ -16,55 +16,54 @@
<div class="col-12" id="doc-col">
<h1 itemprop="name">Google Play services Preview for Wear</h1>
-
-
-
+<div id="jd-content">
+<div class="jd-descr" itemprop="articleBody">
+<p>Google Play services 5.0 is currently being rolled out to the world.
+We usually wait to release the Google Play services SDK until the rollout completes.
+This ensures that your newly-updated apps can run on the most devices as possible.</p>
-
- <div id="jd-content">
- <div class="jd-descr" itemprop="articleBody">
-<p>The Google Play services app is currently being rolled out to the hundreds of millions of
-Android devices and will complete in early July. Because of this, we usually wait to release the Google Play
-services SDK until all users receive the app. This guarantees that your newly-updated apps can
-run on the most devices as possible.</p>
+<p>However, if you want to develop for Android Wear now, complete the following steps to get
+special access to all the things you need to start developing, without waiting for the rollout to
+complete.</p>
-<p>However, if want to develop for Android Wear now, complete the following steps
-to get special access to all the things you need to start developing, without waiting
-for the rollout to complete. </p>
-
-<p class="warning"><b>Warning</b>: Do not publish any apps that use the new Google Play services
-APIs until the rollout is complete. Your apps will break on most user devices, which will
-degrade your user rating.</b>
+<p class="warning"><b>Warning</b>: Do not publish any apps that are built with Google Play services
+SDK 5.0 until the rollout is completed over the coming days. Your apps will break on most user
+devices, which will degrade your apps’ user experience. We’ll publish an update to the
+<a href="http://android-developers.blogspot.com/">Android Developer blog</a> when the rollout is done.
</p>
<h2 style="margin-bottom: 0px;">1. Get Whitelisted for the Preview</h2><hr>
-<p>If you attended Google I/O, your registered Gmail account is automatically whitelisted for these
-preview resources. If you didn't attend Google I/O, sign up below to get access:</p>
+<p>If you attended Google I/O, your registered Google account is automatically whitelisted for
+these preview resources. You're done! Head to Step 2.</p>
-<a href="https://groups.google.com/forum/?hl=en#!forum/io14androidweardev">Get Whitelisted</a>
+<p><strong><em>If you didn't attend Google I/O</em></strong> or want to use a different account,
+click following link to and get whitelisted.</p>
+
+<a style="font-size:24px" href="https://groups.google.com/group/io14androidweardev/subscribe" target="_blank">
+Join the Preview Group</a>
<h2 style="margin-bottom: 0px;">2. Download Required Apps</h2><hr>
-<p>You'll need the following apps to get the most out of Android Wear:</p>
+<p>You'll need the following apps to get the most out of Android Wear. You must be whitelisted
+in the Preview Group above and you must install these apps in this exact order:</p>
-
-<p>Here's a list of the apps you need:</p>
-<ul>
-<li><a href="https://play.google.com/apps/testing/com.google.android.gms">Google Play services</a>: Allows your Wear device to properly communicate with your handheld device. This is
-required to use the Android Wear Companion App.</li>
- <li><a href="https://play.google.com/apps/testing/com.google.android.wearable.app">Android Wear
- Companion</a>: The main user app to pair a handheld to a wearable and to provide syncing
- of notifications and data.</li>
+<ol>
+<li><a href="https://play.google.com/apps/testing/com.google.android.gms">Google Play services</a>:
+Allows your Android Wear device to communicate with your handheld device. This is required to
+use the Android Wear app and other apps listed below.</li>
<li><a href="https://play.google.com/apps/testing/com.google.android.googlequicksearchbox">Google
- Search</a>: A preview release of the Google Search handheld app that Wear communicates with
- to carry out searches.</li>
- <li><a href="https://play.google.com/apps/testing/com.google.android.keep">Google Keep</a>: To enable the "Take a note" command</li>
- <li><a href="https://play.google.com/apps/testing/com.google.samples.apps.iosched">Google I/O 2014</a></li>
- </ul>
+ Search</a>: Enables searches from Android Wear</li>
+ <li><a href="https://play.google.com/apps/testing/com.google.android.wearable.app">Android Wear
+ Companion</a>: The app for pairing a handheld to a wearable and providing syncing of
+ notifications and data
+ </li>
+ <p class="note"><b>Note:</b> After becoming a tester, it can take up to 1 hour to get access to
+ the preview versions of these apps.</p>
+ </ol>
-<p>To enable the preview versions of the apps, click each app link above and follow these
-instructions:</p>
+<p>To obtain these apps from Google Play, click each app link above and follow these instructions,
+preferably from your mobile browser:</p>
<ol>
<li>Click the <b>Become a Tester</b> button to opt-in to the preview version of the app. The page
@@ -72,24 +71,48 @@
<li>Click the <b>Download <app name> from the Play Store</b> link to go to Google Play
Store download page to get the app. The
following screenshot shows how the opt-in process looks like:
-<img style="margin-top:40px" src="/preview/images/opt-in.png"></li>
- <li>When Google Play services is rolled out to all devices, go back to the app links provided
- to opt-out of the preview versions of the apps. Check back here in a week for the status of
- the rollout.</li>
+<img style="margin-top:40px" src="/preview/images/opt-in.png" /></li>
</ol>
+<h2>3. Download Optional Apps</h2>
+<p>Please join the Test Group and install the following apps to enhance your Android Wear experience:
+</p>
+<ol>
+ <li><a href="https://play.google.com/apps/testing/com.google.android.keep">Google Keep</a>:
+ Supports the "Take a note" command</li>
+ <li><a href="https://play.google.com/apps/testing/com.google.samples.apps.iosched">Google I/O
+ 2014</a>:
+ Supports session feedback from Android Wear</li>
+ </ol>
-<h2 style="margin-bottom: 0px;">3. Start Building</h2><hr>
-<p>Check out the <a href="/training/building-wearables">Building Apps for Wearables</a>
+<h2 style="margin-bottom: 0px;">4. Start Building</h2><hr>
+
+<p>The Google Play services SDK is required if you want to sync and send data between wearable
+and handheld devices. To get the new SDK that is compatible with the Google Play services
+APK that you just installed, follow these steps: </p>
+
+<p class="note"><b>Note:</b> Android Studio is required for Wear development.</p>
+<ol>
+ <li><a href="/sdk/installing/studio.html">Download and install Android Studio</a></li>
+ <li>Start SDK Manager.</li>
+ <li>Update the Android SDK Tools and Platform-tools to versions 23 and 20 respectively.</li>
+ <li>Click <b>Tools > Manage Add-on Sites > User Defined Sites</b>.</li>
+ <li>Click <b>New</b>, enter
+ <code>https://dl-ssl.google.com/android/repository/addon-play-services-5.xml</code> into the
+ text field, and click <b>OK</b>.</li>
+ <li>Click Close. You should now see new emulator images that support this preview
+ release of Google Play services and the Google Play services client libraries you need to
+ start developing.</li>
+ <li><a href="http://storage.googleapis.com/androiddevelopers/preview/google-play-services-preview.zip">Download</a> the Google Play
+ services reference documentation for this preview release.</li>.
+</ol>
+
+<p>When you're done here, check out the <a href="/training/building-wearables.html">Building Apps for Wearables</a>
training classes for information on how to build for Wear.</p>
- </div>
-
-
-
- </div> <!-- end jd-content -->
+</div> <!-- end jd-content -->
</div><!-- end doc-content -->
-</div> <!-- end body-content -->
+</div> <!-- end body-content -->
</body>
</html>
\ No newline at end of file
diff --git a/docs/html/preview/images/ActivitySceneTransitionBasic.png b/docs/html/preview/images/ActivitySceneTransitionBasic.png
new file mode 100644
index 0000000..ea58641
--- /dev/null
+++ b/docs/html/preview/images/ActivitySceneTransitionBasic.png
Binary files differ
diff --git a/docs/html/preview/images/ActivitySceneTransitionBasic@2x.png b/docs/html/preview/images/ActivitySceneTransitionBasic@2x.png
new file mode 100644
index 0000000..cd28ade
--- /dev/null
+++ b/docs/html/preview/images/ActivitySceneTransitionBasic@2x.png
Binary files differ
diff --git a/docs/html/preview/images/BasicManagedProfile.png b/docs/html/preview/images/BasicManagedProfile.png
new file mode 100644
index 0000000..7354842
--- /dev/null
+++ b/docs/html/preview/images/BasicManagedProfile.png
Binary files differ
diff --git a/docs/html/preview/images/BasicManagedProfile@2x.png b/docs/html/preview/images/BasicManagedProfile@2x.png
new file mode 100644
index 0000000..c232809
--- /dev/null
+++ b/docs/html/preview/images/BasicManagedProfile@2x.png
Binary files differ
diff --git a/docs/html/preview/images/JobSchedulerSample.png b/docs/html/preview/images/JobSchedulerSample.png
new file mode 100644
index 0000000..ee57bdb
--- /dev/null
+++ b/docs/html/preview/images/JobSchedulerSample.png
Binary files differ
diff --git a/docs/html/preview/images/JobSchedulerSample@2x.png b/docs/html/preview/images/JobSchedulerSample@2x.png
new file mode 100644
index 0000000..3d543db
--- /dev/null
+++ b/docs/html/preview/images/JobSchedulerSample@2x.png
Binary files differ
diff --git a/docs/html/preview/images/hero.jpg b/docs/html/preview/images/hero.jpg
deleted file mode 100644
index 1c52989..0000000
--- a/docs/html/preview/images/hero.jpg
+++ /dev/null
Binary files differ
diff --git a/docs/html/preview/images/material.png b/docs/html/preview/images/material.png
deleted file mode 100644
index 2d807d4..0000000
--- a/docs/html/preview/images/material.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/preview/images/notifications.png b/docs/html/preview/images/notifications.png
deleted file mode 100644
index 2fb2fea..0000000
--- a/docs/html/preview/images/notifications.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/preview/images/notifications/Action.png b/docs/html/preview/images/notifications/Action.png
new file mode 100644
index 0000000..de584a3
--- /dev/null
+++ b/docs/html/preview/images/notifications/Action.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/AntiSample1.png b/docs/html/preview/images/notifications/AntiSample1.png
new file mode 100644
index 0000000..2cb5411
--- /dev/null
+++ b/docs/html/preview/images/notifications/AntiSample1.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/AntiSample3.png b/docs/html/preview/images/notifications/AntiSample3.png
new file mode 100644
index 0000000..5780583
--- /dev/null
+++ b/docs/html/preview/images/notifications/AntiSample3.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Basic.png b/docs/html/preview/images/notifications/Basic.png
new file mode 100644
index 0000000..4ce21c48
--- /dev/null
+++ b/docs/html/preview/images/notifications/Basic.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Expanded.png b/docs/html/preview/images/notifications/Expanded.png
new file mode 100644
index 0000000..9ca260b
--- /dev/null
+++ b/docs/html/preview/images/notifications/Expanded.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/ExpandedImage.png b/docs/html/preview/images/notifications/ExpandedImage.png
new file mode 100644
index 0000000..7e58881
--- /dev/null
+++ b/docs/html/preview/images/notifications/ExpandedImage.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/ExpandedText.png b/docs/html/preview/images/notifications/ExpandedText.png
new file mode 100644
index 0000000..0361695
--- /dev/null
+++ b/docs/html/preview/images/notifications/ExpandedText.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Headsup.png b/docs/html/preview/images/notifications/Headsup.png
new file mode 100644
index 0000000..03015b1
--- /dev/null
+++ b/docs/html/preview/images/notifications/Headsup.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Largelogo.png b/docs/html/preview/images/notifications/Largelogo.png
new file mode 100644
index 0000000..e222422
--- /dev/null
+++ b/docs/html/preview/images/notifications/Largelogo.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/LockScreen.png b/docs/html/preview/images/notifications/LockScreen.png
new file mode 100644
index 0000000..c204a81
--- /dev/null
+++ b/docs/html/preview/images/notifications/LockScreen.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/MusicPlayback.png b/docs/html/preview/images/notifications/MusicPlayback.png
new file mode 100644
index 0000000..58f3d0b
--- /dev/null
+++ b/docs/html/preview/images/notifications/MusicPlayback.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/ProductIcons.png b/docs/html/preview/images/notifications/ProductIcons.png
new file mode 100644
index 0000000..227c8ab
--- /dev/null
+++ b/docs/html/preview/images/notifications/ProductIcons.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Productlogos.png b/docs/html/preview/images/notifications/Productlogos.png
new file mode 100644
index 0000000..baed5ac
--- /dev/null
+++ b/docs/html/preview/images/notifications/Productlogos.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/ReplyAction.png b/docs/html/preview/images/notifications/ReplyAction.png
new file mode 100644
index 0000000..73c9fc6
--- /dev/null
+++ b/docs/html/preview/images/notifications/ReplyAction.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Stack.png b/docs/html/preview/images/notifications/Stack.png
new file mode 100644
index 0000000..90c6c4a
--- /dev/null
+++ b/docs/html/preview/images/notifications/Stack.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Summarise_Do.png b/docs/html/preview/images/notifications/Summarise_Do.png
new file mode 100644
index 0000000..4daaa4d
--- /dev/null
+++ b/docs/html/preview/images/notifications/Summarise_Do.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Summarise_Dont.png b/docs/html/preview/images/notifications/Summarise_Dont.png
new file mode 100644
index 0000000..4094d73
--- /dev/null
+++ b/docs/html/preview/images/notifications/Summarise_Dont.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Summary.png b/docs/html/preview/images/notifications/Summary.png
new file mode 100644
index 0000000..135de83
--- /dev/null
+++ b/docs/html/preview/images/notifications/Summary.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Summary_Do.png b/docs/html/preview/images/notifications/Summary_Do.png
new file mode 100644
index 0000000..c48bfd3
--- /dev/null
+++ b/docs/html/preview/images/notifications/Summary_Do.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/TimeSensitive.png b/docs/html/preview/images/notifications/TimeSensitive.png
new file mode 100644
index 0000000..55a9e59
--- /dev/null
+++ b/docs/html/preview/images/notifications/TimeSensitive.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/Triggered.png b/docs/html/preview/images/notifications/Triggered.png
new file mode 100644
index 0000000..c327ed2
--- /dev/null
+++ b/docs/html/preview/images/notifications/Triggered.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/WearBasic.png b/docs/html/preview/images/notifications/WearBasic.png
new file mode 100644
index 0000000..881f1e5
--- /dev/null
+++ b/docs/html/preview/images/notifications/WearBasic.png
Binary files differ
diff --git a/docs/html/preview/images/notifications/notifications_pattern_priority.png b/docs/html/preview/images/notifications/notifications_pattern_priority.png
new file mode 100644
index 0000000..af2d725
--- /dev/null
+++ b/docs/html/preview/images/notifications/notifications_pattern_priority.png
Binary files differ
diff --git a/docs/html/preview/images/opt-in.png b/docs/html/preview/images/opt-in.png
index 51754af..7151253 100644
--- a/docs/html/preview/images/opt-in.png
+++ b/docs/html/preview/images/opt-in.png
Binary files differ
diff --git a/docs/html/preview/images/volta.png b/docs/html/preview/images/volta.png
deleted file mode 100644
index 9125081..0000000
--- a/docs/html/preview/images/volta.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/preview/index.html b/docs/html/preview/index.html
index ff4572b..4c42d99 100644
--- a/docs/html/preview/index.html
+++ b/docs/html/preview/index.html
@@ -165,7 +165,7 @@
<img src="/preview/images/l-dev-prev.png" style=" margin:0px 0 0 40px" width="860px"/>
<div class="col-6" style="margin-left:660px; margin-top:-105px">
- <a href="/preview/setup-sdk.html" class="landing-button landing-secondary" style="position:absolute;z-index:100;float:right;margin-top: 0px;">Get Started</a><!--
+ <a href="/preview/setup-sdk.html" class="landing-button landing-primary" style="position:absolute;z-index:100;float:right;margin-top: 0px;">Get Started</a><!--
<p>Set up your environment and check out all the docs to get up and running.</p>-->
@@ -241,7 +241,7 @@
Let us know when you encounter problems, so we can fix them and make
the platform better for you and your users.
</p><p class="landing-small">
- <a target="_blank" href="https://code.google.com/p/android-developer-preview/">
+ <a href="https://code.google.com/p/android-developer-preview/">
Report Issues</a>
</p>
<p></p>
@@ -257,7 +257,7 @@
Join the community of Android developers testing out the L Developer Preview and
share your thoughts and experiences.
</p><p class="landing-small">
- <a target="_blank" href="https://plus.sandbox.google.com/communities/113159138894928487684">
+ <a href="http://g.co/androidldevpreview">
Discuss on Google+</a>
</p>
</div>
@@ -274,7 +274,7 @@
for news about the changes.
</p>
<p class="landing-small">
- <a target="_blank" href="/preview/support.html">Get Support</a>
+ <a href="/preview/support.html">Get Support</a>
</p>
</div>
</div>
@@ -358,4 +358,4 @@
<script src="/jd_tag_helpers.js" type="text/javascript"></script>
</body>
-</html>
\ No newline at end of file
+</html>
diff --git a/docs/html/preview/material/animations.jd b/docs/html/preview/material/animations.jd
index cee782a..b8d063b 100644
--- a/docs/html/preview/material/animations.jd
+++ b/docs/html/preview/material/animations.jd
@@ -10,8 +10,9 @@
<li><a href="#reveal">Reveal Effect</a></li>
<li><a href="#transitions">Activity Transitions</a></li>
<li><a href="#curvedmotion">Curved Motion</a></li>
- <li><a href="#viewstate">View State Changes</a></li>
+ <li><a href="#viewstate">Animating View State Changes</a></li>
<li><a href="#drawabletint">Drawable Tinting</a></li>
+ <li><a href="#colorextract">Extracting Colors from an Image</a></li>
</ol>
</div>
</div>
@@ -32,19 +33,26 @@
<h2 id="touch">Touch Feedback</h2>
-<p>In the Android L Developer Preview the default touch feedback animations for buttons use the new
+<p>The default touch feedback animations for buttons use the new
<code>RippleDrawable</code> class, which transitions between different states with a ripple
effect.</p>
-<p>To use this functionality in your custom views, create a <code>RippleDrawable</code> and set
-it as the background of your view. You can define a <code>RippleDrawable</code> as an XML resource
-using the <code>ripple</code> element.</p>
+<p>In most cases, this functionality should be applied in your view XML by specifying the
+background as <code>?android:attr/selectableItemBackground</code> for a bounded ripple or
+<code>?android:attr/selectableItemBackgroundBorderless</code> for a ripple that extends beyond
+the view bounds. You can also create a <code>RippleDrawable</code> and set
+it as the background of your view. Alternatively, you can define a <code>RippleDrawable</code>
+as an XML resource using the <code>ripple</code> element. The
+Android L Developer Preview animates the selection color with a ripple effect.</p>
+
+<p>You can assign a color to <code>RippleDrawable</code> objects. To change the default touch
+feedback color, use the theme's <code>android:colorControlHighlight</code> attribute.</p>
<h2 id="reveal">Reveal Effect</h2>
-<p>The <code>View.createRevealAnimator</code> method enables you to animate a clipping circle
-to reveal or hide a view.</p>
+<p>The <code>ViewAnimationUtils.createCircularReveal</code> method enables you to animate a
+clipping circle to reveal or hide a view.</p>
<p>To reveal a previously invisible view using this effect:</p>
@@ -61,7 +69,8 @@
// create and start the animator for this view
// (the start radius is zero)
-ValueAnimator anim = myView.createRevealAnimator(cx, cy, 0, finalRadius);
+ValueAnimator anim =
+ ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
anim.start();
</pre>
@@ -79,7 +88,8 @@
int initialRadius = myView.getWidth();
// create the animation (the final radius is zero)
-ValueAnimator anim = myView.createRevealAnimator(cx, cy, initialRadius, 0);
+ValueAnimator anim =
+ ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0);
// make the view invisible when the animation is done
anim.addListener(new AnimatorListenerAdapter() {
@@ -97,25 +107,48 @@
<h2 id="transitions">Activity Transitions</h2>
-<p>The Android L Developer Preview enables your app to customize the default animations for
-activity transitions. You can specify custom animations for enter and exit transitions and for
+<p>You can specify custom animations for enter and exit transitions and for
transitions of shared elements between activities.</p>
<ul>
- <li>An <strong>enter</strong> transition determines how views in an activity enter the scene.
- For example, in the <em>explode</em> enter transition the views enter the scene from outside
- and fly in towards the center of the screen.</li>
+<li>An <strong>enter</strong> transition determines how views in an activity enter the scene.
+For example, in the <em>explode</em> enter transition, the views enter the scene from the outside
+and fly in towards the center of the screen.</li>
- <li>An <strong>exit</strong> transition determines how views in an activity exit the scene. For
- example, in the <em>explode</em> exit transition the views exit the scene away from the
- center.</li>
+<li>An <strong>exit</strong> transition determines how views in an activity exit the scene. For
+ example, in the <em>explode</em> exit transition, the views exit the scene away from the
+center.</li>
- <li>A <strong>shared elements</strong> transition determines how views that are shared between
- two activities transition between these activities. For example, if two activities have the same
- image in different positions and sizes, the <em>moveImage</em> shared element transition
- translates and scales the image smoothly between these activities.</li>
+<li>A <strong>shared elements</strong> transition determines how views that are shared between
+two activities transition between these activities. For example, if two activities have the same
+image in different positions and sizes, the <em>moveImage</em> shared element transition
+translates and scales the image smoothly between these activities.</li>
</ul>
+<p>The Android L Developer Preview supports these enter and exit transitions:</p>
+
+<ul>
+<li><em>explode</em> - Moves views in or out from the center of the scene.</li>
+<li><em>slide</em> - Moves views in or out from one of the edges of the scene.</li>
+<li><em>fade</em> - Mades views in or out of the scene.</li>
+</ul>
+
+<p>Any transition that extends the <code>android.transition.Visibility</code> class is supported
+as an enter or exit transition. For more information, see the API reference for the
+<code>android.transition.Transition</code> class.</p>
+
+<p>The Android L Developer Preview also supports these shared elements transitions:</p>
+
+<ul>
+<li><em>changeBounds</em> - Animates the changes in layout bounds of target views.</li>
+<li><em>changeClipBounds</em> - Animates the changes in clip bounds of target views.</li>
+<li><em>changeTransform</em> - Animates the changes in scale and rotation of target views.</li>
+<li><em>moveImage</em> - Animates changes in size and scale type for an image view.</li>
+</ul>
+
+<p>When you enable activity transitions in your app, the default cross-fading transition is
+activated between the entering and exiting activities.</p>
+
<img src="/preview/material/images/SceneTransition.png" alt=""
id="figure1" style="width:600px;margin-top:20px"/>
<p class="img-caption">
@@ -125,7 +158,8 @@
<h3>Specify custom transitions</h3>
<p>First, enable window content transitions with the <code>android:windowContentTransitions</code>
-attribute when you define a style that inherits from the material theme:</p>
+attribute when you define a style that inherits from the material theme. You can also specify
+enter, exit, and shared element transitions in your style definition:</p>
<pre>
<style name="BaseAppTheme" parent="android:Theme.Material">
@@ -144,21 +178,13 @@
</style>
</pre>
-<p>You can also specify enter, exit, and shared element transitions in your style definition.
-The <code>move_image</code> transition in this example is defined as follows:</p>
+<p>The <code>move_image</code> transition in this example is defined as follows:</p>
<pre>
<!-- res/transition/move_image.xml -->
<!-- (see also Shared Transitions below) -->
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
- <moveImage>
- <targets>
- <!-- shared view in the first activity -->
- <target android:targetId="@id/image_small" />
- <!-- shared view in the second activity -->
- <target android:targetId="@id/image_big" />
- </targets>
- </moveImage>
+ <moveImage/>
</transitionSet>
</pre>
@@ -170,7 +196,7 @@
<code>Window.requestFeature</code> method:</p>
<pre>
-// inside your activity
+// inside your activity (if you did not enable transitions in your theme)
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
// set an exit transition
@@ -187,6 +213,20 @@
<li><code>Window.setSharedElementExitTransition</code></li>
</ul>
+<p>The <code>setExitTransition</code> and <code>setSharedElementExitTransition</code> methods
+define the exit transition for the calling activity. The <code>setEnterTransition</code> and
+<code>setSharedElementEnterTransition</code> methods define the enter transition for the called
+activity.</p>
+
+<p>To get the full effect of a transition, you must enable window content transitions on both the
+calling and called activities. Otherwise, the calling activity will start the exit transition,
+but then you'll see a window transition (like scale or fade).</p>
+
+<p>To start an enter transition as soon as possible, use the
+<code>Window.setAllowEnterTransitionOverlap</code> method on the called activity. This lets you
+have more dramatic enter transitions. The same applies for the calling activity and exit
+transitions with the <code>Window.setAllowExitTransitionOverlap</code> method.</p>
+
<h3>Start an activity using transitions</h3>
<p>If you enable transitions and set an exit transition for an activity, the transition is activated
@@ -201,7 +241,7 @@
<ol>
<li>Enable window content transitions in your style.</li>
<li>Specify a shared elements transition in your style.</li>
-<li>Define your transition as an XML resource specifying the IDs of the target views.</li>
+<li>Define your transition as an XML resource.</li>
<li>Assign a common name to the shared elements in both layouts with the
<code>android:viewName</code> attribute.</li>
<li>Use the <code>ActivityOptions.makeSceneTransitionAnimation</code> method.</li>
@@ -212,7 +252,7 @@
final View imgContainerView = findViewById(R.id.img_container);
// get the common element for the transition in this activity
-final View androidRobotView = findViewById(R.id.android_robot_img);
+final View androidRobotView = findViewById(R.id.image_small);
// define a click listener
imgContainerView.setOnClickListener(new View.OnClickListener() {
@@ -232,6 +272,9 @@
<p>For shared dynamic views that you generate in your code, use the <code>View.setViewName</code>
method to specify a common element name in both activities.</p>
+<p>To reverse the scene transition animation when you finish the second activity, call the
+<code>Activity.finishAfterTransition</code> method instead of <code>Activity.finish</code>.</p>
+
<h3>Multiple shared elements</h3>
<p>To make a scene transition animation between two activities that have more than one shared
@@ -241,12 +284,8 @@
<pre>
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this,
- new Pair[] {
Pair.create(view1, "agreedName1"),
- Pair.create(view2, "agreedName2"),
- ...
- }
-);
+ Pair.create(view2, "agreedName2"));
</pre>
@@ -279,7 +318,7 @@
</ul>
<p>You can pass a <code>PathInterpolator</code> object to the
-<code>Animation.setInterpolation</code> method.</p>
+<code>Animator.setInterpolation</code> method.</p>
<p>The <code>ObjectAnimator</code> class has new constructors that enable you to animate
coordinates along a path using two or more properties at once. For example, the following animator
@@ -293,20 +332,20 @@
</pre>
-<h2 id="viewstate">View State Changes</h2>
+<h2 id="viewstate">Animating View State Changes</h2>
<p>The new <code>StateListAnimator</code> class lets you define animators that run when the state
of a view changes. The following example shows how to define an <code>StateListAnimator</code> as
an XML resource:</p>
<pre>
-<!-- animate the elevation property of a view when pressed -->
+<!-- animate the translationZ property of a view when pressed -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<set>
- <objectAnimator android:propertyName="elevation"
+ <objectAnimator android:propertyName="translationZ"
android:duration="100"
- android:valueTo="60"
+ android:valueTo="2"
android:valueType="floatType"/>
<!-- you could have other objectAnimator elements
here for "x" and "y", or other properties -->
@@ -316,15 +355,19 @@
android:state_pressed="false"
android:state_focused="true">
<set>
- <objectAnimator android:propertyName="elevation"
+ <objectAnimator android:propertyName="translationZ"
android:duration="100"
- android:valueTo="10"
+ android:valueTo="2"
android:valueType="floatType"/>
</set>
</item>
</selector>
</pre>
+<p class="note"><strong>Note:</strong> There is a known issue in the L Developer Preview release
+that requires valueFrom values to be provided in StateListAnimator animations to get the correct
+behavior.</p>
+
<p>The new <code>AnimatedStateListDrawable</code> class lets you create drawables that show
animations between state changes of the associated view. Some of the system widgets in the
Android L Developer Preview use these animations by default. The following example shows how
@@ -337,9 +380,9 @@
<!-- provide a different drawable for each state-->
<item android:id="@+id/pressed" android:drawable="@drawable/drawableP"
- android:state-pressed="true"/>
+ android:state_pressed="true"/>
<item android:id="@+id/focused" android:drawable="@drawable/drawableF"
- android:state-focused="true"/>
+ android:state_focused="true"/>
<item android:id="@id/default"
android:drawable="@drawable/drawableD"/>
@@ -358,21 +401,41 @@
<h2 id="drawabletint">Drawable Tinting</h2>
-<p>The Android L Developer Preview enables you to define bitmaps as an alpha mask and to tint
-them using a color resource or a theme attribute that resolves to a color resource. You can
-create these assets only once and color them automatically to match your theme.</p>
+<p>The Android L Developer Preview enables you to define bitmaps or nine-patches as alpha masks and
+to tint them using a color resource or a theme attribute that resolves to a color resource (for
+example, <code>?android:attr/colorPrimary</code>). You can create these assets only once and color them
+automatically to match your theme.</p>
-<p>To apply a tint to a bitmap in your code, use the <code>setTint</code> method in these
-classes:</p>
+<p>To apply a tint to a bitmap, use the <code>setTint</code> method or the <code>android:tint</code>
+attribute for <code>BitmapDrawable</code> and <code>NinePatchDrawable</code>.</p>
+
+<p>The <code>setTint</code> method also lets you set the Porter-Duff mode used to blend the
+tint color for <code>NinePatchDrawable</code> and <code>BitmapDrawable</code> objects in your code.
+To set the tint mode in your layouts, use the <code>android:tintMode</code> attribute.</p>
+
+
+<h2 id="colorextract">Extracting Prominent Colors from an Image</h2>
+
+<p>The Android L Developer Preview Support Library includes the <code>Palette</code> class,
+which lets you extract prominent colors from an image. This class extracts the following
+prominent colors:</p>
<ul>
-<li><code>PaintDrawable</code></li>
-<li><code>NinePatchDrawable</code></li>
-<li><code>RippleDrawable</code></li>
+<li>Vibrant</li>
+<li>Vibrant dark</li>
+<li>Vibrant light</li>
+<li>Muted</li>
+<li>Muted dark</li>
+<li>Muted light</li>
</ul>
-<p>In your layouts, use the <code>android:tint</code> attribute instead.</p>
+<p>To extract these colors, pass a <code>Bitmap</code> object to the
+<code>Palette.generate</code> static method in the background thread where you load your images.
+If you can't use that thread, call the <code>Palette.generateAsync</code> method instead and
+provide a listener.</p>
-<p>The <code>setTint</code> method also lets you set the tint blending mode for
-<code>NinePatchDrawable</code> and <code>RippleDrawable</code> objects in your code. To set the
-tint mode in your layouts, use the <code>android:tintMode</code> attribute.</p>
+<p>To retrieve the prominent colors from the image, use the getter methods in the
+<code>Palette</code> class, such as <code>Palette.getVibrantColor</code>.</p>
+
+<p>For more information, see the API reference for the
+<code>android.support.v7.graphics.Palette</code> class.</p>
\ No newline at end of file
diff --git a/docs/html/preview/material/compatibility.jd b/docs/html/preview/material/compatibility.jd
index ce04e9e..fb97112 100644
--- a/docs/html/preview/material/compatibility.jd
+++ b/docs/html/preview/material/compatibility.jd
@@ -14,7 +14,7 @@
</div>
</div>
-<p>The new material design features (like the material theme and custom animations) are only
+<p>The new material design features (like the material theme and activity transitions) are only
available in the Android L Developer Preview. However, you can design your apps to make use of
these features when running on devices with the Android L Developer Preview and still be
compatible with previous releases of Android.</p>
@@ -49,15 +49,34 @@
and your alternative layout files for earlier versions of Android inside <code>res/layout/</code>.
Alternative layouts have the same file name.</p>
+<p>To avoid duplication of code, define your styles inside <code>res/values/</code> and modify the
+styles in <code>res/values-v21/</code> for the new APIs.</p>
+
<h2 id="widgets">UI Widgets</h2>
<p>The <code>RecyclerView</code> and <code>CardView</code> widgets are included in the Android L
-Developer Preview Support Library, so they are available in earlier versions of Android.</p>
+Developer Preview Support Library, so they are available in earlier versions of Android with
+these limitations:</p>
+
+<ul>
+<li><code>CardView</code> falls back to a programmatic shadow implementation using additional padding.</li>
+<li><code>CardView</code> does not clip its children views that intersect with rounded corners.</li>
+</ul>
+
+<p>These limitations do not apply to the Android L Developer Preview.</p>
<h2 id="animation">Animation APIs</h2>
-<p>The new APIs for custom animations are only available in the Android L Developer Preview. To
-preserve compatibility with earlier verisons of Android, check the system version at runtime before
-you invoke these APIs.</p>
\ No newline at end of file
+<p>The following new APIs are only available in the Android L Developer Preview:</p>
+
+<ul>
+<li>Activity transitions</li>
+<li>Touch feedback</li>
+<li>Reveal animations</li>
+<li>Path-based animations</li>
+</ul>
+
+<p>To preserve compatibility with earlier verisons of Android, check the system version at
+runtime before you invoke these APIs.</p>
\ No newline at end of file
diff --git a/docs/html/preview/material/get-started.jd b/docs/html/preview/material/get-started.jd
index 9c0e55d..7d0625e 100644
--- a/docs/html/preview/material/get-started.jd
+++ b/docs/html/preview/material/get-started.jd
@@ -8,9 +8,9 @@
<ol>
<li><a href="#applytheme">Apply the Material Theme</a></li>
<li><a href="#layouts">Design Your Layouts</a></li>
- <li><a href="#depth">Specify Depth in Your Views</a></li>
+ <li><a href="#depth">Specify Elevation in Your Views</a></li>
<li><a href="#widgets">Use the New UI Widgets</a></li>
- <li><a href="#apis">Use the New APIs</a></li>
+ <li><a href="#animations">Customize Your Animations</a></li>
</ol>
</div>
</div>
@@ -19,7 +19,8 @@
<ol>
<li style="margin-bottom:10px">
- Take a look at the <a href="">material design specification</a>.</li>
+ Take a look at the <a href="http://www.google.com/design/spec">material design
+ specification</a>.</li>
<li style="margin-bottom:10px">
Apply the material <strong>theme</strong> to your app.</li>
<li style="margin-bottom:10px">
@@ -27,11 +28,11 @@
<li style="margin-bottom:10px">
Create your <strong>layouts</strong> following material design guidelines.</li>
<li style="margin-bottom:10px">
- Specify the <strong>depth</strong> for views to cast appropriate shadows.</li>
+ Specify the <strong>elevation</strong> of your views to cast appropriate shadows.</li>
<li style="margin-bottom:10px">
Use the new <strong>widgets</strong> for complex views, such as lists and cards.</li>
<li style="margin-bottom:10px">
- Use the new <strong>APIs</strong> to customize the animations in your app.</li>
+ Use the new APIs to customize the <strong>animations</strong> in your app.</li>
</ol>
<h3>Update Your App for the Android L Developer Preview</h3>
@@ -42,15 +43,16 @@
<h3>Create New Apps for the Android L Developer Preview</h3>
-<p>If you are creating a new app for the Android L Developer Preview, the material design
-guidelines provide you with a solid design framework for your app. Follow these guidelines and
+<p>If you are creating a new app for the Android L Developer Preview, the <a
+href="http://www.google.com/design/spec">material design guidelines</a> provide you with a
+cohesive design framework for your app. Follow these guidelines and
use the new functionality in the Android framework to design and develop your app.</p>
<h2 id="applytheme">Apply the Material Theme</h2>
<p>To apply the material theme in your app, specify a style that inherits from
-<code>android:theme.Material</code>:</p>
+<code>android:Theme.Material</code>:</p>
<pre>
<!-- res/values/styles.xml -->
@@ -70,8 +72,8 @@
<h2 id="layouts">Design Your Layouts</h2>
<p>In addition to applying and customizing the material theme, your layouts should conform to
-the material design guidelines. When you design your layouts, pay special attention to the
-following:</p>
+the <a href="http://www.google.com/design/spec">material design guidelines</a>. When you design
+your layouts, pay special attention to the following:</p>
<ul>
<li>Baseline grids</li>
@@ -81,37 +83,36 @@
<li>Layout structure</li>
</ul>
-<p>You still define layouts inside XML files using the standard tools from the Android framework.
-For details on the material design guidelines, see the <a href="">material design
-specification</a>.</p>
+<h2 id="depth">Specify Elevation in Your Views</h2>
-<h2 id="depth">Specify Depth in Your Views</h2>
-
-<p>In the Android L Developer Preview, views can cast shadows. The elevation value of a view
-determines the size of its shadow. To set the elevation of a view, use the
+<p>Views can cast shadows, and the elevation value of a view
+determines the size of its shadow and its drawing order. To set the elevation of a view, use the
<code>android:elevation</code> attribute in your layouts:</p>
<pre>
-<Button
- android:id="@+id/my_button"
+<TextView
+ android:id="@+id/my_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/next"
- <strong>android:elevation</strong>="10dp" />
+ android:background="@color/white"
+ <strong>android:elevation</strong>="5dp" />
</pre>
+<p>The new <code>translationZ</code> property lets you create animations that reflect temporary
+changes in the elevation of a view. For example, this is useful to respond to touch gestures.</p>
+
<p>For more details, see <a href="{@docRoot}preview/material/views-shadows.html">Views and
Shadows</a>.</p>
<h2 id="widgets">Use the New UI Widgets</h2>
-<p>The Android L Developer Preview includes two new UI widgets for complex views,
-<code>RecyclerView</code> and <code>CardView</code>. <code>RecyclerView</code> is a more advanced
-version of <code>ListView</code> that provides performance improvements and is easier to use.
-<code>CardView</code> lets you show pieces of information inside cards with a consistent look
-across apps. To include a <code>CardView</code> in your layout:</p>
+<p><code>RecyclerView</code> is a more advanced version of <code>ListView</code> that provides
+performance improvements and is easier to use. <code>CardView</code> lets you show pieces of
+information inside cards with a consistent look across apps. To include a <code>CardView</code>
+in your layout:</p>
<pre>
<android.support.v7.widget.CardView
@@ -126,7 +127,7 @@
<p>For more information, see <a href="{@docRoot}preview/material/ui-widgets.html">UI Widgets</a>.</p>
-<h2 id="apis">Use the APIs to Customize Your Animations</h2>
+<h2 id="animations">Customize Your Animations</h2>
<p>The Android L Developer Preview includes new APIs to create custom animations in your app.
For example, you can enable activity transitions and define an exit transition inside an
diff --git a/docs/html/preview/material/images/MaterialDark.png b/docs/html/preview/material/images/MaterialDark.png
index 6a72280..f1018af 100644
--- a/docs/html/preview/material/images/MaterialDark.png
+++ b/docs/html/preview/material/images/MaterialDark.png
Binary files differ
diff --git a/docs/html/preview/material/images/MaterialLight.png b/docs/html/preview/material/images/MaterialLight.png
index 0e85528..4ed7d5c 100644
--- a/docs/html/preview/material/images/MaterialLight.png
+++ b/docs/html/preview/material/images/MaterialLight.png
Binary files differ
diff --git a/docs/html/preview/material/images/card_travel.png b/docs/html/preview/material/images/card_travel.png
index a804ca0..19752a8 100644
--- a/docs/html/preview/material/images/card_travel.png
+++ b/docs/html/preview/material/images/card_travel.png
Binary files differ
diff --git a/docs/html/preview/material/images/list_mail.png b/docs/html/preview/material/images/list_mail.png
index ca53ee1..e70291c 100644
--- a/docs/html/preview/material/images/list_mail.png
+++ b/docs/html/preview/material/images/list_mail.png
Binary files differ
diff --git a/docs/html/preview/material/index.jd b/docs/html/preview/material/index.jd
index b7abcb4..9628e3a 100644
--- a/docs/html/preview/material/index.jd
+++ b/docs/html/preview/material/index.jd
@@ -1,13 +1,15 @@
page.title=Material Design
page.type=design
+page.image=images/material.png
+page.metaDescription=Learn how to apply material design to your apps.
@jd:body
-<p itemprop="description">The Android L Developer Preview includes support for material design apps. Material design
-is a comprehensive guide for visual, motion, and interaction design across platforms and devices.
-To use material design in your Android apps, follow the guidelines defined in the
-<a href="">material design specification</a> and use the new components and functionality
-available in the Android L Developer Preview.</p>
+<p itemprop="description">The Android L Developer Preview includes support for material design
+apps. Material design is a comprehensive guide for visual, motion, and interaction design across
+platforms and devices. To use material design in your Android apps, follow the guidelines defined
+in the <a href="http://www.google.com/design/spec">material design specification</a> and use the
+new components and functionality available in the Android L Developer Preview.</p>
<p>The Android L Developer Preview provides the following elements for you to build material
design apps:</p>
@@ -27,13 +29,13 @@
<!-- two columns -->
<div style="width:700px;margin-top:25px;margin-bottom:20px">
<div style="float:left;width:250px;margin-left:40px;margin-right:60px;">
- <img src="{@docRoot}preview/material/images/MaterialDark.png" style="width:250px;"/>
+ <img src="{@docRoot}preview/material/images/MaterialDark.png" width="500" height="238"/>
<div style="width:140px;margin:0 auto">
<p style="margin-top:8px">Dark Material theme</p>
</div>
</div>
<div style="float:left;width:250px;margin-right:0px;">
- <img src="{@docRoot}preview/material/images/MaterialLight.png" style="width:250px;"/>
+ <img src="{@docRoot}preview/material/images/MaterialLight.png" width="500" height="238"/>
<div style="width:140px;margin:0 auto">
<p style="margin-top:8px">Light Material theme</p>
</div>
@@ -49,12 +51,12 @@
<!-- two columns -->
<div style="width:700px;margin-top:25px;margin-bottom:20px">
<div style="float:left;width:250px;margin-left:40px;margin-right:60px;">
- <img src="{@docRoot}preview/material/images/list_mail.png" style="width:250px;"/>
+ <img src="{@docRoot}preview/material/images/list_mail.png" width="500" height="426"/>
<p>The new <code>RecyclerView</code> widget is a more advanced version of <code>ListView</code>
- provides performance improvements for dynamic views and is easier to use.</p>
+ that provides performance improvements for dynamic views and is easier to use.</p>
</div>
<div style="float:left;width:250px;margin-right:0px;">
- <img src="{@docRoot}preview/material/images/card_travel.png" style="width:250px;"/>
+ <img src="{@docRoot}preview/material/images/card_travel.png" width="500" height="426"/>
<p>The new <code>CardView</code> widget lets you display important pieces of information inside
cards that have a consistent look and feel.</p>
</div>
@@ -64,9 +66,13 @@
<h3>View Shadows</h3>
-<p>In addition to the X and Y components, views in the Android L Developer Preview have a Z
-component. This new component represents the elevation of a view, which determines the size of
-its shadow: views with higher Z values cast bigger shadows.</p>
+<p>In addition to the X and Y properties, views in the Android L Developer Preview have a Z
+property. This new property represents the elevation of a view, which determines:</p>
+
+<ul>
+<li>The size of the shadow - Views with higher Z values cast bigger shadows.</li>
+<li>The drawing order - Views with higher Z values appear on top of other views.</li>
+</ul>
<div style="width:290px;margin-left:35px;float:right">
<div class="framed-nexus5-port-span-5">
@@ -86,7 +92,7 @@
<p>The Android L Developer Preview provides new APIs that let you create custom animations for
touch feedback in UI controls, view state changes, and activity transitions.</p>
-<p>The new animation APIs in the Android L Developer Preview let you:</p>
+<p>The new animation APIs let you:</p>
<ul>
<li style="margin-bottom:15px">
@@ -99,7 +105,7 @@
Switch between activities with custom <strong>activity transition</strong> animations.
</li>
<li style="margin-bottom:15px">
-Create custom animation patterns with <strong>curved motion</strong>.
+Create more natural animations with <strong>curved motion</strong>.
</li>
<li style="margin-bottom:15px">
Animate changes in one or more view properties with <strong>view state change</strong> animations.
@@ -109,9 +115,16 @@
</li>
</ul>
+<p>Touch feedback animations are built into several standard views, such as buttons. The new APIs
+let you customize these animations and add animations to your custom views.</p>
+
<h3>New Capabilities for Drawables</h3>
<p>The Android L Developer Preview supports <strong>drawable tinting</strong>: you can define
-bitmaps as an alpha mask and tint them using a color resource. You can create these assets only
-once and color each instance to match your theme.</p>
+bitmaps as an alpha mask and tint them using a color resource. You create these assets only
+once and color each instance to match your theme. Drawables also now support specifying most XML
+properties as <strong>theme attributes</strong>.</p>
+
+<p>The Android L Developer Preview Support Library includes a <strong>color extraction</strong>
+library that lets you automatically extract prominent colors from a bitmap image.</p>
\ No newline at end of file
diff --git a/docs/html/preview/material/theme.jd b/docs/html/preview/material/theme.jd
index b954960..740bf56 100644
--- a/docs/html/preview/material/theme.jd
+++ b/docs/html/preview/material/theme.jd
@@ -8,7 +8,7 @@
<ol>
<li><a href="#colorpalette">Customize the Colot Palette</a></li>
<li><a href="#statusbar">Customize the Status Bar</a></li>
- <li><a href="#inheritance">Theme Inheritance</a></li>
+ <li><a href="#inheritance">Theme Individual Views</a></li>
</ol>
</div>
</div>
@@ -21,18 +21,18 @@
<li>Activity transition animations</li>
</ul>
-<p>The Android L Developer Preview lets you easily customize the look of the material theme
-according to your brand identity with a color palette you control. You can tint the app bar and
+<p>You can customize the look of the material theme
+according to your brand identity with a color palette you control. You can tint the action bar and
the status bar using theme attributes, as shown in Figure 1.</p>
-<div style="float:right;margin-left:25px;margin-top:-25px">
+<div style="float:right;margin-left:25px;margin-top:-50px">
<img src="{@docRoot}preview/material/images/ThemeColors.png" style="width:250px"/>
-<p class="img-caption"><strong>Figure 1.</strong> Customizing the material theme.</p>
+<p class="img-caption" style="margin-bottom:0px">
+<strong>Figure 1.</strong> Customizing the material theme.</p>
</div>
-<p>The system widgets have a new design and touch feedback animations. Activity transitions help
-users navigate your app by providing visual continuity. You can customize the color palette,
-the touch feedback animations, and the activity transitions for your app.</p>
+<p>The system widgets have a new design and touch feedback animations. You can customize the
+color palette, the touch feedback animations, and the activity transitions for your app.</p>
<p>The material theme is defined as:</p>
@@ -43,7 +43,7 @@
</ul>
<p>For a list of material styles that you can use, see the API reference for
-<code>android.R.styles</code>.</p>
+<code>android.R.style</code>.</p>
<p class="note">
<strong>Note:</strong> The material theme is only available in the Android L Developer Preview.
@@ -53,8 +53,8 @@
<h2 id="colorpalette">Customize the Color Palette</h2>
-<p>To customize the theme's base colors to fit your brand, define your custom colors using
-theme attributes when you inherit from the material theme:</p>
+<p style="margin-bottom:30px">To customize the theme's base colors to fit your brand, define
+your custom colors using theme attributes when you inherit from the material theme:</p>
<pre>
<resources>
@@ -63,38 +63,40 @@
<!-- Main theme colors -->
<!-- your app's branding color (for the app bar) -->
<item name="android:colorPrimary">@color/primary</item>
- <!-- darker variant of colorPrimary (for contextual app bars) -->
+ <!-- darker variant of colorPrimary (for status bar, contextual app bars) -->
<item name="android:colorPrimaryDark">@color/primary_dark</item>
-
- <!-- other theme colors -->
- <item name="android:colorButtonNormal">@color/button_normal</item>
- <item name="android:windowBackground">@color/wbackground</item>
+ <!-- theme UI controls like checkboxes and text fields -->
+ <item name="android:colorAccent">@color/accent</item>
</style>
</resources>
</pre>
-<h2 id="statusbar">Customize the Status Bar</h2>
+<h2 id="statusbar">Customize the Status and Navigation Bar</h2>
<p>The material theme lets you easily customize the status bar, so you can specify a
-color which fits your brand and provides enough contrast to show the white status icons. To
+color that fits your brand and provides enough contrast to show the white status icons. To
set a custom color for the status bar, use the <code>android:statusBarColor</code> attribute when
-you extend the material theme.</p>
+you extend the material theme. By default, <code>android:statusBarColor</code> inherits the
+value of <code>android:colorPrimaryDark</code>.</p>
<p>To handle the color of the status bar yourself (for example, by adding a gradient in the
background), set the <code>android:statusBarColor</code> attribute to
-<code>@android:color/transparent</code>. You can also use the
-<code>Window.setStatusBarColor</code> method for animations or fading.</p>
+<code>@android:color/transparent</code> and adjust the window flags as required. You can
+also use the <code>Window.setStatusBarColor</code> method for animations or fading.</p>
<p class="note"><strong>Note:</strong>
The status bar should almost always have a clear delineation from the primary toolbar, except for
full-bleed imagery cases and when you use a gradient as a protection.
</p>
+<p>When customizing the navigation and status bars, make them both transparent or modify only
+the status bar. The navigation bar should remain black in all other cases.</p>
-<h2 id="inheritance">Theme Inheritance</h3>
-<p>In the Android L Developer Preview, elements in XML layout definitions can specify the
-<code>android:theme</code> attribute, which references a theme resource. This attribute modifies
-the theme for the element and any elements inflated below it, which is useful to alter theme
-color palettes in a specific portion of an interface.</p>
\ No newline at end of file
+<h2 id="inheritance">Theme Individual Views</h3>
+
+<p>Elements in XML layout definitions can specify the <code>android:theme</code> attribute,
+which references a theme resource. This attribute modifies the theme for the element and any
+elements inflated below it, which is useful to alter theme color palettes in a specific portion
+of an interface.</p>
\ No newline at end of file
diff --git a/docs/html/preview/material/ui-widgets.jd b/docs/html/preview/material/ui-widgets.jd
index f18bff9..31604d6 100644
--- a/docs/html/preview/material/ui-widgets.jd
+++ b/docs/html/preview/material/ui-widgets.jd
@@ -14,19 +14,20 @@
<p>The support library in the Android L Developer Preview contains two new widgets,
<code>RecyclerView</code> and <code>CardView</code>. Use these widgets to show complex lists
-and cards in your app. These widgets have material design styles and animations by default.</p>
+and cards in your app. These widgets have material design style by default.</p>
<h2 id="recyclerview">RecyclerView</h2>
-<p><code>RecyclerView</code> is a more advanced version of <code>ListView</code>. This widget is
-a container for large sets of views that can be recycled and scrolled very efficiently. Use the
-<code>RecyclerView</code> widget when you have lists with elements that change dynamically.</p>
+<p><code>RecyclerView</code> is a more advanced and flexible version of <code>ListView</code>.
+This widget is a container for large sets of views that can be recycled and scrolled very
+efficiently. Use the <code>RecyclerView</code> widget when you have lists with elements that
+change dynamically.</p>
<p><code>RecyclerView</code> is easy to use, because it provides:</p>
<ul>
- <li>A set of layout managers for positioning items</li>
+ <li>A layout manager for positioning items</li>
<li>Default animations for common item operations</li>
</ul>
@@ -34,20 +35,9 @@
widget.</p>
<p>To use the <code>RecyclerView</code> widget, you have to specify an adapter and a layout
-manager. An <strong>adapter</strong> provides a binding from a dataset to views that are displayed
-within a <code>RecyclerView</code>. For example, if your dataset is an array of strings displayed
-as <code>TextView</code> items, the layout manager asks the adapter to:
-</p>
-
-<ul>
- <li>Set the text of an existing <code>TextView</code> to one of the strings in the dataset</li>
- <li>Create new <code>TextView</code> objects</li>
- <li>Determine the size of the dataset</li>
-</ul>
-
-<p>To create an adapter, you extend the <code>RecyclerView.Adapter</code> class. The details of
-the implementation depend on the specifics of your dataset and the type of views. Fore more
-information, see the examples below.</p>
+manager. To create an adapter, you extend the <code>RecyclerView.Adapter</code> class. The details
+of the implementation depend on the specifics of your dataset and the type of views. For more
+information, see the <a href="#rvexamples">examples</a> below.</p>
<img src="/preview/material/images/RecyclerView.png" alt="" id="figure1" style="width:550px"/>
<p class="img-caption">
@@ -62,16 +52,17 @@
<code>findViewById</code> lookups.
</p>
-<p><code>RecyclerView</code> provides two layout managers you can use:</p>
+<p><code>RecyclerView</code> provides <code>LinearLayoutManager</code>, which shows the items in a
+vertical or horizontal scrolling list. To create a custom layout, you extend the
+<code>RecyclerView.LayoutManager</code> class.</p>
-<ul>
- <li><code>LinearLayoutManager</code> shows the items in a vertically scrolling list.</li>
- <li><code>GridLayoutManager</code> shows the items in a rectangular grid.</li>
-</ul>
+<h3>Animations</h3>
-<p>To create a custom layout, you extend the <code>RecyclerView.LayoutManager</code> class.</p>
+<p>Animations for adding and removing items are enabled by default in <code>RecyclerView</code>.
+To customize these animations, extend the <code>RecyclerView.ItemAnimator</code> class and use
+the <code>RecyclerView.setItemAnimator</code> method.</p>
-<h3>Examples</h3>
+<h3 id="rvexamples">Examples</h3>
<p>To include a <code>RecyclerView</code> in your layout:</p>
@@ -87,7 +78,7 @@
<p>To get the <code>RecyclerView</code> object in your activity:</p>
<pre>
-public class MyActivity extends ActionBarActivity {
+public class MyActivity extends Activity {
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
@@ -98,7 +89,8 @@
setContentView(R.layout.my_activity);
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
- // improve performance if the size is fixed
+ // improve performance if you know that changes in content
+ // do not change the size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
@@ -139,7 +131,8 @@
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
- View v = new TextView(parent.getContext());
+ View v = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.my_text_view, null);
// set the view's size, margins, paddings and layout parameters
...
ViewHolder vh = new ViewHolder(v);
@@ -167,22 +160,30 @@
<h2 id="cardview">CardView</h2>
<p><code>CardView</code> extends the <code>FrameLayout</code> class and lets you show information
-inside a card with optional rounded corners:</p>
+inside cards that have a consistent look on any app. <code>CardView</code> widgets can have
+shadows and rounded corners.</p>
+
+<p>To create a card with a shadow, use the <code>android:elevation</code> attribute.
+<code>CardView</code> uses real elevation and dynamic shadows
+and falls back to a programmatic shadow implementation on earlier versions. For more information,
+see <a href="{@docRoot}preview/material/compatibility.html">Compatibility</a>.</p>
+
+<p>Here's how to specify properties of <code>CardView</code>:</p>
<ul>
<li>To set the corner radius in your layouts, use the <code>android:cardCornerRadius</code>
attribute.</li>
<li>To set the corner radius in your code, use the <code>CardView.setRadius</code> method.</li>
+ <li>To set the background color of a card, use the <code>android:cardBackgroundColor</code>
+attribute.</li>
</ul>
-<p>To set the background color of a card, use the <code>android:cardBackgroundColor</code>
-attribute.</p>
-
<p>To include a <code>CardView</code> in your layout:</p>
<pre>
<!-- A CardView that contains a TextView -->
<android.support.v7.widget.CardView
+ xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_gravity="center"
android:layout_width="200dp"
diff --git a/docs/html/preview/material/views-shadows.jd b/docs/html/preview/material/views-shadows.jd
index c5884d6..f7682f5 100644
--- a/docs/html/preview/material/views-shadows.jd
+++ b/docs/html/preview/material/views-shadows.jd
@@ -13,10 +13,12 @@
</div>
</div>
-<p>In apps with material design, depth has meaning. You should assign higher elevation values to more
-important UI elements in your app. The elevation value of a view determines the size of its
-shadow: views with higher Z values cast bigger shadows. Views only cast shadows on the Z=0 plane
-under an orthographic projection (the views do not scale for different values of Z).</p>
+<p>The elevation of a view determines the size of its shadow:
+views with higher Z values cast bigger shadows. Views only cast shadows on the Z=0 plane under an
+orthographic projection (the views do not scale for different values of Z).</p>
+
+<p>Elevation is also useful to create animations where widgets temporarily rise above the
+view plane when performing some action.</p>
<h2 id="elevation">View Elevation</h2>
@@ -35,52 +37,59 @@
<p>To set the translation of a view, use the <code>View.setTranslationZ</code> method.</p>
-<p>The Z values are measured in the same units as the X and Y values (like <code>dp</code> or
-<code>px</code>).</p>
+<p>The new <code>ViewPropertyAnimator.z</code> and <code>ViewPropertyAnimator.translationZ</code>
+methods enable you to easily animate the elevation of views. For more information, see
+the API reference for <code>ViewPropertyAnimator</code> and the <a
+href="{@docRoot}guide/topics/graphics/prop-animation.html#object-animator">Property Animation</a>
+developer guide.</p>
+
+<p>The Z values are measured in the same units as the X and Y values.</p>
<h2 id="shadows">Shadows and Outlines</h2>
-<p>The bounds of a view's background drawable determine the default shape of its shadow. To define
-a custom shape for a shadow, such as an oval, use the <code>View.setOutline</code> method:</p>
+<p>The bounds of a view's background drawable determine the default shape of its shadow.
+<strong>Outlines</strong> represent the outer shape of a graphics object and define the ripple
+area for touch feedback.</p>
+
+<p>For example, if you define a view with a background drawable:</p>
<pre>
-View v = findViewById(R.id.my_view);
-
-// add 10px to the static elevation
-v.setTranslationZ(10);
-
-// set an oval shadow
-Outline outline = new Outline();
-outline.setOval(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
-myView.setOutline(outline);
+<TextView
+ android:id="@+id/myview"
+ ...
+ android:elevation="2dp"
+ android:background="@drawable/myrect" />
</pre>
-<p>An <code>Outline</code> represents the outer shape of a graphics object. You can create
-<code>Outline</code> objects as in this example, or you can obtain the outline from a
-<code>Drawable</code> object with the <code>getOutline</code> method.</p>
+<p>where the background drawable is defined as a rectangle with rounded corners:</p>
-<p>The outline of a view also defines the ripple area for touch feedback.</p>
+<pre>
+<!-- res/drawable/myrect.xml -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#42000000" />
+ <corners android:radius="5dp" />
+</shape>
+</pre>
+
+<p>Then this view and drawable cast the appropiate shadow.</p>
+
+<p>You can also create outlines in your code using the methods in the <code>Outline</code> class,
+and you can assign them to views with the <code>View.setOutline</code> method.</p>
<p>To prevent a view from casting a shadow, set its outline to <code>null</code>.</p>
<h2 id="clip">Clipping Views</h2>
-<p>The Android L Developer Preview lets you clip a view to its outline area using the
+<p>Clip a view to its outline area using the
<code>View.setClipToOutline</code> method. Only rectangle, circle, and round rectangle outlines
support clipping, as determined by the <code>Outline.canClip</code> method.</p>
-<p>To determine if a view has been clipped, use the <code>View.getClipToOutline</code> method.</p>
+<p>To clip a view to the shape of a drawable, set the drawable as the background of the view
+(as shown above) and call the <code>View.setClipToOutline</code> method.</p>
-<pre>
-// clip a view to an oval
-View v = findViewById(R.id.my_view);
-outline.setOval(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
-myView.setOutline(outline);
-
-// if the view is not already clipped
-if (v.getClipToOutline() == false) {
- v.setClipToOutline(true);
-}
-</pre>
\ No newline at end of file
+<p>Because clipping views is an expensive operation, don't animate the shape you use to
+clip a view. To achieve this effect, use a <a
+href="{@docRoot}preview/material/animations.html#reveal">Reveal Effect</a> animation.</p>
\ No newline at end of file
diff --git a/docs/html/preview/notifications.jd b/docs/html/preview/notifications.jd
new file mode 100644
index 0000000..e0fb7be
--- /dev/null
+++ b/docs/html/preview/notifications.jd
@@ -0,0 +1,617 @@
+page.title=Design for Notifications
+page.tags="notifications","design","L"
+@jd:body
+
+<p>The notification system allows users to keep informed about relevant and timely
+events in your app, such as new chat messages from a friend or a calendar event.
+Think of notifications as a news channel that alerts the user to important events as
+they happen or a log that chronicles events while the user is not paying attention -
+and one that is synced as appropriate across all their Android devices.</p>
+
+<h4 id="New"><strong>New in L</strong></h4>
+
+<p>In L, notifications receive an important structural visual and functional update:</p>
+
+<ul>
+ <li> Visual changes to notifications as part of material design</li>
+ <li> Notifications are now available on the device lockscreen, yet sensitive content can still
+ be hidden behind it</li>
+ <li> A new presentation format called Heads-up for receiving high priority notifications while
+ using the device</li>
+ <li> Cloud-synced notifications - act on a notification on your Android tablet and it is also
+ dismissed on your phone.</li>
+ <li> And starting now (in Android 4.4W, API Level 20, the platform release for Android Wear),
+ your notifications will bridge to Android Wear devices. You can extend the functionality of
+ notifications on Wear in two different ways. First, you can add speech input and canned responses
+ to Actions on Wear, allowing users to complete tasks from their wrists. Second, you can write
+ Wear apps that hook into your notifications to go even further in terms of creating interactive
+ experiences for users.</li>
+</ul>
+
+<h2 id="Anatomy">Anatomy of a notification</h2>
+
+<p>This section goes over basic parts of a notification and how they can
+appear on different types of devices.</p>
+
+<h3 id="BaseLayout">Base Layout</h3>
+
+<p>At a minimum, all notifications consist of a base layout, including:</p>
+
+<ul>
+ <li> The notification's <strong>icon</strong>, symbolizing the originating app, and also potentially the kind of notification if the app has several substantially different sorts of notifications it can post</li>
+ <li> A notification <strong>title</strong> and additional <strong>text</strong></li>
+ <li> A <strong>timestamp</strong></li>
+</ul>
+
+<p>Notifications created with <code>Notification.Builder</code> for versions of Android earlier than L will look and work the same in L, with only minor stylistic changes that the system handles for you.</p>
+
+<div style="margin-top:20px" >
+ <div class="col-7">
+ <img src="{@docRoot}preview/images/notifications/Basic.png"
+ alt=""/>
+ </div>
+ <div class="col-4" style="padding-top:60px; text-align:center" >
+ <img src="{@docRoot}preview/images/notifications/WearBasic.png"
+ alt="" width="162" height="162" />
+ </div>
+</div>
+
+<div style="clear:both;">
+ <p class="img-caption">
+ Base layout of a handheld notification and the same notification on Wear,
+ with a user photo and a notification icon
+ </p>
+ </div>
+</div>
+
+<h3 id="ExpandedLayouts">Expanded layouts</h3>
+
+
+<p>You have the option to provide more details on notifications. You can use this to show the first few lines of a message or show a larger image preview. This provides the user with additional context, and - in some cases - may allow the user to read a message in its entirety. The user can pinch-zoom or perform a single-finger glide in order to toggle between compact and expanded layouts. For single event notifications, Android provides three expanded layout templates (text, inbox, and image) for you to re-use in your application. The following images show you how they look on handhelds and wearables.</p>
+
+<div class="col-5" style="margin-top:20px">
+ <img src="{@docRoot}preview/images/notifications/ExpandedText.png"
+ alt="" />
+ <img style="margin-top:30px" src="{@docRoot}preview/images/notifications/Stack.png"
+ alt=""/>
+ <img style="margin-top:30px" src="{@docRoot}preview/images/notifications/ExpandedImage.png"
+ alt="" />
+</div>
+
+<div class="col-6" style="margin-top:20px">
+ <img style="margin-top:60px" src="{@docRoot}preview/images/notifications/Expanded.png"
+ alt="" />
+
+ <img style="margin-top:140px" src="{@docRoot}preview/images/notifications/figure6.png"
+ alt="" />
+</div>
+
+<h3 id="actions" style="clear:both">Actions</h3>
+
+<p>Android has supported optional actions that are displayed at the bottom of the notification, as far back as Jelly Bean. With actions, users can handle the most common tasks for a particular notification from within the notification shade without having to open the originating application. This speeds up interaction and, in conjunction with "swipe-to-dismiss", helps users to streamline their notification triaging experience.</p>
+
+
+<div class="col-6" style="margin-top:20px">
+ <img src="{@docRoot}preview/images/notifications/Action.png"
+ alt="" />
+ <p class="img-caption">
+ Calendar reminder notification with two actions
+ </p>
+</div>
+<div class="col-5" style="margin-top:20px">
+ <img src="{@docRoot}preview/images/notifications/ReplyAction.png" width="156px" height="156px"
+ alt="" />
+ <p class="img-caption">
+ Gmail new message notification - the actions appear to the right of the main card on Wear devices
+ </p>
+</div>
+
+
+<p style="clear:both">Be judicious with how many actions you include with a notification. The more actions you include, the more cognitive complexity you create. Limit yourself to the fewest number of actions possible by only including the most imminently important and meaningful ones.</p>
+
+<p>Good candidates for actions on notifications are actions that:</p>
+
+<ul>
+ <li> Are essential, frequent and typical for the content type you're displaying
+ <li> Allow the user to accomplish tasks quickly
+</ul>
+
+<p>Avoid actions that are:</p>
+
+<ul>
+ <li> Ambiguous
+ <li> Duplicative of the default action of the notification (such as "Read" or "Open")
+</ul>
+
+
+
+<p>You can specify a maximum of three actions, each consisting of an action icon and an action name. Adding actions to a simple base layout will make the notification expandable, even if the notification doesn't have an expanded layout. Since actions are only shown for expanded notifications and are otherwise hidden, you must make sure that any action a user can invoke from a notification is available from within the associated application as well.</p>
+
+<h2 id="notifications_on_android_wear">Notifications on Android Wear</h2>
+
+<p>Additionally, notifications and their actions are bridged over to Wear devices by default. Developers have control to control which notifications from bridging from the phone to the watch and vice versa. And developers can control which actions bridge as well. If your app includes actions that can't be accomplished with a single tap, either hide these actions on your Wear notification or consider hooking them up to a Wear app to allow the user to finish the action on their watch.</p>
+
+<div class="col-7">
+<h4>Bridging notifications</h4>
+
+<p><strong>Notifications that should be bridged</strong></p>
+
+<ul>
+ <li> New instant messages</li>
+</ul>
+
+<p><strong>Don't bridge</strong></p>
+
+<ul>
+ <li> If a podcasting app has new episodes available for download,
+ keep this notification on the phone.</li>
+</ul>
+</div>
+
+<div class="col-4" style="margin-top:20px">
+ <img src="{@docRoot}preview/images/notifications/WearBasic.png" width="156px" height="156px"
+ alt="" />
+</div>
+
+
+<div style="clear:left" class="col-7">
+<h4>Bridging actions</h4></p>
+
+<p><strong>Actions to bridge</strong></p>
+
+<ul>
+ <li> Single tap actions such as +1, Like, Heart</li>
+</ul>
+
+<p><strong>Actions not to bridge</strong></p>
+
+<ul>
+ <li> Actions that map to features that aren't possible on the watch</li>
+</ul>
+
+<p><b>Unique actions to define for Wear</b></p>
+
+<ul>
+ <li> Quick lists of canned responses such as "Be right back"</li>
+ <li> Open on phone</li>
+ <li> A "Comment" or "Reply" action that brings up the speech input screen</li>
+ <li> Actions that can launch Wear-specific apps</li>
+</ul>
+</div>
+
+<div class="col-4" style="margin-top:220px">
+ <img src="{@docRoot}preview/images/notifications/ReplyAction.png" width="156px" height="156px"
+ alt="" />
+</div>
+
+<h2 style="clear:left">Heads-up Notification</h2>
+<div class="figure" style="width:262px">
+ <img src="{@docRoot}preview/images/notifications/Headsup.png"
+ alt="" width="220" id="figure9" />
+ <p class="img-caption">
+ <strong>Figure 6.</strong> Example of a Heads-up notification (incoming phone call, high priority) coming in on top of an immersive app
+ </p>
+</div>
+
+<p>When notifications with priority set to High (see right) arrives, it is presented to users for a short period of time on the device with an expanded layout with its actions exposed.</p>
+<p> After this period of time, it retreats back to the Notification shade. If a notification is flagged as High or Max or a full-screen takeover, it gets a HUN in L.</p>
+
+<p><b>Good examples of Heads-up notifications</b></p>
+
+<ul>
+ <li> Incoming phone call when using device</li>
+ <li> Alarm when using device</li>
+ <li> New SMS message</li>
+ <li> Low battery</li>
+</ul>
+
+<h2 style="clear:both" id="guidelines">Guidelines</h2>
+
+
+<div class="figure" style="width:366px; margin-top:40px">
+ <img src="{@docRoot}preview/images/notifications/Triggered.png"
+ alt="" width="366" height="142" />
+ <p class="img-caption">
+ Notification that shows the person who triggered it and the content they are sending you
+ </p>
+</div>
+
+<h3 id="MakeItPersonal">Make it personal</h3>
+
+<p>For notifications of items sent by another person (such as a message or status update), include that person's image using setLargeIcon. Also attach information about the person to the notification's metadata (see EXTRA_PEOPLE).</p>
+
+<p>Your notification's main icon will still be shown, so the user can associate it with the icon visible in the status bar.</p>
+
+<h3 id="navigate_to_the_right_place">Navigate to the right place</h3>
+
+<p>When the user touches the body of a notification (outside of the action buttons), open your app to the place where the user can view and act upon the data referenced in the notification. In most cases this will be the detail view of a single data item such as a message, but it might also be a summary view if the notification is stacked (see <em>Stacked notifications</em> below) and references multiple items. If in any of those cases the user is taken to a hierarchy level below your app's top-level, insert navigation into your app's back stack to allow them to navigate to your app's top level using the system back button. For more information, see the chapter on <em>System-to-app navigation</em> in the <a href="/design/patterns/navigation.html">Navigation</a> design pattern.</p>
+
+<h3 id="correctly_set_and_manage_notification_priority">Correctly set and manage notification priority</h3>
+
+<p>Starting with Jelly Bean, Android supported a priority flag for notifications. It allows you to influence where your notification will appear in comparison to other notifications and help to make sure that users always see their most important notifications first. You can choose from the following priority levels when posting a notification:</p>
+<table>
+ <tr>
+ <td class="tab0">
+<p><strong>Priority</strong></p>
+</td>
+ <td class="tab0">
+<p><strong>Use</strong></p>
+</td>
+ </tr>
+ <tr>
+ <td class="tab1">
+<p><code>MAX</code></p>
+</td>
+ <td class="tab1">
+<p>Use for critical and urgent notifications that alert the user to a condition that is time-critical or needs to be resolved before they can continue with a particular task.</p>
+</td>
+ </tr>
+ <tr>
+ <td class="tab1">
+<p><code>HIGH</code></p>
+</td>
+ <td class="tab1">
+<p>Use high priority notifications primarily for important communication, such as message or chat events with content that is particularly interesting for the user. High priority notifications will get the Heads-Up Notification display starting in L.</p>
+</td>
+ </tr>
+ <tr>
+ <td class="tab1">
+<p><code>DEFAULT</code></p>
+</td>
+ <td class="tab1">
+<p>The default priority. Keep all notifications that don't fall into any of the other categories at this priority level.</p>
+</td>
+ </tr>
+ <tr>
+ <td class="tab1">
+<p><code>LOW</code></p>
+</td>
+ <td class="tab1">
+<p>Use for notifications that you still want the user to be informed about, but that rate low in urgency. LOW notifications will tend to show up at the bottom of the list, which makes them a good choice for things like pubic/undirected social updates: the user has asked to be notified about them, but they should never take precedence over urgent or direct communication.</p>
+</td>
+ </tr>
+ <tr>
+ <td class="tab1">
+<p><code>MIN</code></p>
+</td>
+ <td class="tab1">
+<p>Contextual/background information (e.g. weather information, contextual location information). Minimum priority notifications will not show in the status bar. The user will only discover them when they expand the notification shade.</p>
+</td>
+ </tr>
+</table>
+
+
+<h4 id="how_to_choose_an_appropriate_priority"><strong>How to choose an appropriate
+priority</strong></h4>
+
+<p>Default, High, and Max priority are interruptive priority levels and risk interrupting the user from what they are doing. This should not not be taken lightly, so these levels should be reserved for notifications that:</p>
+
+<ul>
+ <li> Involve another person</li>
+ <li> Are time-sensitive</li>
+ <li> Might immediately change the user's behavior in the real world</li>
+</ul>
+
+<p>Notifications set to Low and Min can still be very valuable for the user. Many if not most notifications just don't need to command the user's immediate attention, or vibrate the user's wrist, yet contain information that they will find valuable when they choose to look for notifications. Criteria for Low and Min priority notifications:</p>
+
+<ul>
+ <li> Don't involve other people</li>
+ <li> Aren't time sensitive</li>
+ <li> Is content the user might be interested in but could choose to browse at their leisure</li>
+</ul>
+
+
+ <img src="{@docRoot}preview/images/notifications/notifications_pattern_priority.png"
+ alt="" width="700"/>
+
+
+<h3 style="clear:both" id="set_a_notification_category">Set a notification category</h3>
+
+<p>If your notification falls into one of the predefined categories (see below), assign it accordingly. Aspects of the system UI such as the notification shade (or any other notification listener) may use this information to make ranking and filtering decisions.</p>
+<table>
+ <tr>
+ <td>
+<p><code>Notification.CATEGORY_CALL</code></p>
+</td>
+ <td>
+<p>Incoming call (voice or video) or similar synchronous communication request</p>
+</td>
+ </tr>
+ <tr>
+ <td>
+<p><code>Notification.CATEGORY_MESSAGE</code></p>
+</td>
+ <td>
+<p>Incoming direct message (SMS, instant message, etc.)</p>
+</td>
+ </tr>
+ <tr>
+ <td>
+<p><code>Notification.CATEGORY_EMAIL</code></p>
+</td>
+ <td>
+<p>Asynchronous bulk message (email)</p>
+</td>
+ </tr>
+ <tr>
+ <td>
+<p><code>Notification.CATEGORY_EVENT</code></p>
+</td>
+ <td>
+<p>Calendar event</p>
+</td>
+ </tr>
+ <tr>
+ <td>
+<p><code>Notification.CATEGORY_PROMO</code></p>
+</td>
+ <td>
+<p>Promotion or advertisement</p>
+</td>
+ </tr>
+ <tr>
+ <td>
+<p><code>Notification.CATEGORY_ALARM</code></p>
+</td>
+ <td>
+<p>Alarm or timer</p>
+</td>
+ </tr>
+ <tr>
+ <td>
+<p><code>Notification.CATEGORY_PROGRESS</code></p>
+</td>
+ <td>
+<p>Progress of a long-running background operation</p>
+</td>
+ </tr>
+ <tr>
+ <td>
+<p><code>Notification.CATEGORY_SOCIAL</code></p>
+</td>
+ <td>
+<p>Social network or sharing update</p>
+</td>
+ </tr>
+ <tr>
+ <td>
+<p><code>Notification.CATEGORY_ERROR</code></p>
+</td>
+ <td>
+<p>Error in background operation or authentication status</p>
+</td>
+ </tr>
+ <tr>
+ <td>
+<p><code>Notification.CATEGORY_TRANSPORT</code></p>
+</td>
+ <td>
+<p>Media transport control for playback</p>
+</td>
+ </tr>
+ <tr>
+ <td>
+<p><code>Notification.CATEGORY_SYSTEM</code></p>
+</td>
+ <td>
+<p>System or device status update. Reserved for system use.</p>
+</td>
+ </tr>
+ <tr>
+ <td>
+<p><code>Notification.CATEGORY_SERVICE</code></p>
+</td>
+ <td>
+<p>Indication of running background service</p>
+</td>
+ </tr>
+ <tr>
+ <td>
+<p><code>Notification.CATEGORY_RECOMMENDATION</code></p>
+</td>
+ <td>
+<p>A specific, timely recommendation for a single thing. For example, a news app might want to recommend a news story it believes the user will want to read next.</p>
+</td>
+ </tr>
+ <tr>
+ <td>
+<p><code>Notification.CATEGORY_STATUS</code></p>
+</td>
+ <td>
+<p>Ongoing information about device or contextual status</p>
+</td>
+ </tr>
+</table>
+
+
+<p> </p>
+
+<h3 id="summarize_your_notifications">Summarize your notifications</h3>
+
+<p>If your app creates a notification while another of the same type is still pending, avoid creating an altogether new notification object. Instead, turn it into a summary notification for the app.</p>
+
+<p>A summary notification builds a summary description and allows the user to understand how many notifications of a particular kind are pending.</p>
+
+<div class="col-6">
+<p><strong>Don't</strong></p>
+
+ <img src="{@docRoot}preview/images/notifications/Summarise_Dont.png"
+ alt="" width="300" />
+
+</div>
+
+<div class="col-5">
+<p><strong>Do</strong></p>
+
+ <img src="{@docRoot}preview/images/notifications/Summarise_Do.png"
+ alt="" width="300"/>
+</div>
+</div>
+
+
+<p style="clear:left">You can provide more detail about the individual notifications that make up a summary by using the expanded digest layout. This allows users to gain a better sense of which notifications are pending and if they are interesting enough to be read in detail within the associated app.</p>
+
+ <img src="{@docRoot}preview/images/notifications/Stack.png" style="margin-bottom:30px"
+ alt="" width="370" />
+ <p class="img-caption">
+ Expanded and contracted notification that is a summary (using InboxStyle)
+ </p>
+
+
+<h3 style="clear:right" id="make_notifications_optional">Make notifications optional</h3>
+
+<p>Users should always be in control of notifications. Allow the user to diszable your app's
+notifications or change their alert properties, such as alert sound and whether to use vibration,
+by adding a notification settings item to your application settings.</p>
+
+<h3 id="use_distinct_icons">Use distinct icons</h3>
+<p>By glancing at the notification area, the user should be able to discern what kinds of
+notifications are currently pending.</p>
+
+<div class="figure">
+ <img src="{@docRoot}preview/images/notifications/ProductIcons.png"
+ alt="" width="420" />
+</div>
+
+ <div><p><strong>Do</strong></p>
+ <p>Look at the notification icons Android apps already provide and create notification icons for
+ your app that are sufficiently distinct in appearance.</p>
+
+ <p><strong>Do</strong></p>
+ <p>Use the proper <a href="/design/style/iconography.html#notification">notification icon
+ style</a> for small icons, and the Material Light
+ <a href="/design/style/iconography.html#action-bar">action bar icon style</a> for your action
+ icons. Do not place any additional alpha (dimming or fading) into your small icons and action
+ icons; they can have anti-aliased edges, but because L uses these icons as masks (that is, only
+ the alpha channel is used), the image should generally be drawn at full opacity.</p>
+<p ><strong>Do</strong></p>
+<p >Keep your icons visually simple and avoid excessive detail that is hard to discern.</p>
+
+</div>
+<p style="clear:both"><strong>Don't</strong></p>
+
+<p>Use color to distinguish your app from others. Notification icons should only be a white-on-transparent background image.</p>
+
+
+<h3 id="pulse_the_notification_led_appropriately">Pulse the notification LED appropriately</h3>
+
+<p>Many Android devices contain a notification LED, which is used to keep the user informed about events while the screen is off. Notifications with a priority level of MAX, HIGH, or DEFAULT should cause the LED to glow, while those with lower priority (LOW and MIN) should not.</p>
+
+<p>The user's control over notifications should extend to the LED. When you use DEFAULT_LIGHTS, the LED will glow with a white color. Your notifications shouldn't use a different color unless the user has explicitly customized it.</p>
+
+<h2 id="building_notifications_that_users_care_about">Building notifications that users care about</h2>
+
+<p>To create an app that users love, it is important to design your notifications carefully. Notifications embody your app's voice, and contribute to your app's personality. Unwanted or unimportant notifications can annoy the user or make them resent how much attention the app wants from them, so use notifications judiciously.</p>
+
+<h3 id="when_to_display_a_notification">When to display a notification</h3>
+<div class="figure">
+ <img src="{@docRoot}preview/images/notifications/TimeSensitive.png"
+ alt="" width="360" />
+ <p class="img-caption">
+ Time sensitive notification examples
+ </p>
+</div>
+<p>To create an application that people enjoy using, it's important to recognize that the user's attention and focus is a resource that must be protected. While Android's notification system has been designed to minimize the impact of notifications on the user's attention, it is nonetheless still important to be aware of the fact that notifications are interrupting the user's task flow. As you plan your notifications, ask yourself if they are important enough to warrant an interruption. If you are unsure, allow the user to opt into a notification using your apps notification settings or adjust the notifications priority flag to Low or Min to avoid distracting the user while they are doing something else.</p>
+
+<p>While well behaved apps generally only speak when spoken to, there are some limited cases where an app actually should interrupt the user with an unprompted notification.</p>
+
+<p>Notifications should be used primarily for <strong>time sensitive events</strong>, and especially if these synchronous events <strong>involve other people</strong>. For instance, an incoming chat is a real time and synchronous form of communication: there is another user actively waiting on you to respond. Calendar events are another good example of when to use a notification and grab the user's attention, because the event is imminent, and calendar events often involve other people.</p>
+
+<h3 style="clear:both" id="when_not_to_display_a_notification">When not to display a notification</h3>
+
+<div class="figure" style="margin-top:60px">
+ <img src="{@docRoot}preview/images/notifications/AntiSample1.png"
+ alt="" width="280px" />
+</div>
+
+<p>There are however many other cases where notifications should not be used:</p>
+
+<ul>
+ <li> Avoid notifying the user of information that is not directed specifically at them, or information that is not truly time sensitive. For instance the asynchronous and undirected updates flowing through a social network generally do not warrant a real time interruption. For the users that do care about them, allow them to opt-in.
+ <li> Don't create a notification if the relevant new information is currently on screen. Instead, use the UI of the application itself to notify the user of new information directly in context. For instance, a chat application should not create system notifications while the user is actively chatting with another user.
+ <li> Don't interrupt the user for low level technical operations, like saving or syncing information, or updating an application, if it is possible for the system to simply take care of itself without involving the user.
+ <li> Don't interrupt the user to inform them of an error if it is possible for the application to recover from the error on its own without the user taking any action.
+ <li> Don't create notifications that have no true notification content and merely advertise your app. A notification should provide useful, timely, new information and should not be used to merely launch an app.
+ <li> Don't create superfluous notifications just to get your brand in front of users. Such notifications will only frustrate and likely alienate your audience. The best way to provide the user with a small amount of updated information and to keep them engaged with your application is to develop a widget that they can choose to place on their home screen.
+</ul>
+
+<h2 style="clear:left" id="interacting_with_notifications">Interacting With Notifications</h2>
+
+<p>Notifications are indicated by icons in the status bar and can be accessed by opening the notification drawer.</p>
+
+<p>Touching a notification opens the associated app to detailed content matching the notification. Swiping left or right on a notification removes it from the list.</p>
+
+<h3 id="ongoing_notifications">Ongoing notifications</h3>
+<div class="figure" style="width:337px">
+ <img src="{@docRoot}preview/images/notifications/MusicPlayback.png"
+ alt="" width="337" />
+ <p class="img-caption">
+ Ongoing notification due to music playback
+ </p>
+</div>
+<p>Ongoing notifications keep users informed about an ongoing process in the background. For example, music players announce the currently playing track in the notification system and continue to do so until the user stops the playback. They can also be used to show the user feedback for longer tasks like downloading a file, or encoding a video. Ongoing notifications cannot be manually removed from the notification drawer.</p>
+
+<p>The L lockscreen doesn't show transport controls for RCC (RemoteControlClient)s anymore. But the lockscreen <em>does</em> show notifications, so each app's playback notification is now the primary way for users to control playback from a locked state. This gives apps more control over which buttons to show and in what way, while providing a consistent experience for the user whether on the lockscreen or unlocked.</p>
+
+<h3 style="clear:both" id="dialogs_and_toasts_are_for_feedback_not_notification">Dialogs
+and toasts are for feedback not notifications</h3>
+
+<p>Your app should not create a dialog or toast if it is not currently on screen. Dialogs and Toasts should only be displayed as the immediate response to the user taking an action inside of your app. For further guidance on the use of dialogs and toasts, refer to <a href="/design/patterns/confirming-acknowledging.html">Confirming & Acknowledging</a>.</p>
+
+<h3>Ranking and Ordering</h3>
+
+<p>Notifications are "news" and so they are essentially shown in reverse-chronological order, with special consideration given to the app's stated notification priority.</p>
+
+<p>In L, notifications are now a key part of the lockscreen, and are featured prominently every time the device display comes on. Because space on the lockscreen is tight, it is more important than ever to identify the most urgent or relevant notifications.</p>
+
+<p>Therefore, L has a more sophisticated sorting algorithm for notifications, taking into account:</p>
+
+<ul>
+ <li> The timestamp and application's stated priority, as before.
+ <li> Whether the notification has recently disturbed the user with sound or vibration. (That is, if the phone just made noise, and the user wants to know "what just happened?" the lockscreen should answer that at a glance.)
+ <li> Any people that are attached to the notification using <code>EXTRA_PEOPLE</code>, and in particular whether those are starred contacts.
+</ul>
+
+<p>To best take advantage of this sorting, developers should focus on the user experience they want to create rather than aiming for any particular spot on the list.</p>
+
+ <img src="{@docRoot}preview/images/notifications/AntiSample3.png"
+ alt="" width="700px" />
+
+ <p class="img-caption" style="margin-top:20px">Gmail notifications are default priority, so they normally sort below messages from an instant messaging app like Hangouts, but Gmail will get a temporary bump when new messages come in.
+ </p>
+
+
+<h3>On the lockscreen</h3>
+
+<p>Starting in L, notifications are visible on the lockscreen, and so we must consider the user's privacy. Notifications often contain sensitive information, and we must take care when showing it to anyone who picks up the device and turns on the display.</p>
+
+<ul>
+ <li> For devices without a secure lockscreen, a simple slide gesture unlocks the whole device. Therefore, Android will always show the complete contents of all notifications on insecure lockscreens.
+ <li> When a device has a secure lockscreen (PIN, pattern, or password), however, it divides the interface into two spheres: "public", the things that are displayed atop a secure lockscreen and can therefore be seen by anyone; and "private", the world behind that lockscreen, which can only be accessed by supplying the correct authentication.
+</ul>
+
+
+
+<h3>The user decides what shows on the secure lockscreen</h3>
+<div class="figure" style="width:249px">
+ <img src="{@docRoot}preview/images/notifications/LockScreen.png"
+ alt="" width="249" height="482" id="figure22" />
+ <p class="img-caption">
+ Notifications on the lockscreen followed by the Pattern Unlock when the user attempts to unlock the phone.
+ </p>
+</div>
+
+<p>When setting up a secure lockscreen, the user can choose to conceal sensitive details from atop the secure lockscreen. In this case the SystemUI considers the notification's <em>visibility level</em> to figure out what can safely be shown.</p>
+
+<p>To control the visibility level, call <code>android.app.Notification.Builder.setVisibility()</code> and specify one of these values:</p>
+
+<ul>
+ <li><code>android.app.Notification.VISIBILITY_PUBLIC</code>. Shows the notification's full content. This is the system default if visibility is left unspecified.
+ <li><code>android.app.Notification.VISIBILITY_PRIVATE</code>. The lockscreen will reveal basic information about the existence of this notification, including its icon and the name of the app that posted it. The rest of the notification's details, however, are not displayed.
+ <ul>
+ <li> If you want to provide a different public version of your notification for the system to display on a secure lockscreen, supply a replacement Notification object in the <code>android.app.Notification.publicVersion</code> field.
+ <li> This is an app's opportunity to create a redacted version of the content that is still useful but does not reveal personal information.
+ <li> <strong>Example: </strong>An SMS app whose notifications include the text of the SMS and the sender's name and contact icon. This notification should be <code>VISIBILITY_PRIVATE</code>, but the <code>publicVersion</code> could still contain useful information like "3 new messages" without any other identifying details.
+ </ul>
+ <li><code>android.app.Notification.VISIBILITY_SECRET</code>. Shows only the most minimal information, excluding even the notification's icon.
+</ul>
\ No newline at end of file
diff --git a/docs/html/preview/preview_toc.cs b/docs/html/preview/preview_toc.cs
index 75703a8..a505905 100644
--- a/docs/html/preview/preview_toc.cs
+++ b/docs/html/preview/preview_toc.cs
@@ -24,6 +24,12 @@
</li>
<li class="nav-section">
+ <div class="nav-section-header empty">
+ <a href="<?cs var:toroot ?>preview/notifications.html">Notification Design</a></div>
+ </li>
+
+
+ <li class="nav-section">
<div class="nav-section-header">
<a href="<?cs var:toroot ?>preview/tv/index.html">TV</a>
</div>
@@ -32,6 +38,19 @@
Get Started</a></li>
<li class="nav-section">
<div class="nav-section-header">
+ <a href="<?cs var:toroot ?>preview/tv/design/index.html">
+ Design</a></div>
+ <ul>
+ <li><a href="<?cs var:toroot ?>preview/tv/design/principles.html">
+ Creative Vision</a></li>
+ <li><a href="<?cs var:toroot ?>preview/tv/design/patterns.html">
+ UI Patterns</a></li>
+ <li><a href="<?cs var:toroot ?>preview/tv/design/style.html">
+ Style</a></li>
+ </ul>
+ </li>
+ <li class="nav-section">
+ <div class="nav-section-header">
<a href="<?cs var:toroot ?>preview/tv/ui/index.html">
User Interface</a></div>
<ul>
@@ -80,9 +99,9 @@
<a href="<?cs var:toroot ?>preview/license.html">License Agreement</a>
</div>
</li>
- <li class="nav-section" style="margin: 20px 0 0 3px;">
- <div class="nav-section-header paging-links empty">
- <a href="<?cs var:toroot ?>index.html" class="prev-page-link">Developer Home</a>
+ <li class="nav-section" style="margin: 20px 0 0 -10px;">
+ <div class="nav-section-header empty">
+ <a href="<?cs var:toroot ?>index.html" class="back-link">Developer Home</a>
</div>
</li>
</ul>
diff --git a/docs/html/preview/reference.jd b/docs/html/preview/reference.jd
index f70f7a2..b70e4e5 100644
--- a/docs/html/preview/reference.jd
+++ b/docs/html/preview/reference.jd
@@ -2,12 +2,10 @@
@jd:body
-<p>The reference documentation and API difference report are available as downloadable packages.
+<p>The reference documentation and API difference report are available in this downloadable package.
</p>
<ul>
- <li><a href="{@docRoot}preview/l-developer-preview-reference.zip">L
+ <li><a href="http://storage.googleapis.com/androiddevelopers/preview/l-developer-preview-reference.zip">L
Developer Preview reference</a></li>
- <li><a href="{@docRoot}preview/l-developer-preview-api-diff.zip">L
- Developer Preview difference report</a></li>
</ul>
\ No newline at end of file
diff --git a/docs/html/preview/samples.jd b/docs/html/preview/samples.jd
index 9bccb31..155ae21 100644
--- a/docs/html/preview/samples.jd
+++ b/docs/html/preview/samples.jd
@@ -2,4 +2,236 @@
@jd:body
-<p> </p>
\ No newline at end of file
+<p>The following code samples are provided for the L Developer Preview. You can
+download them in the Android SDK Manager under the <b>SDK Samples</b> component
+for the L Developer Preview.</p>
+
+<p class="note">
+ <strong>Note:</strong> At this time, the downloadable projects are designed
+ for use with Gradle and Android Studio.
+</p>
+
+
+<h3 id="BasicManagedProfile">BasicManagedProfile</h3>
+<div class="figure" style="width:220px">
+ <img src="{@docRoot}preview/images/BasicManagedProfile.png"
+ srcset="{@docRoot}preview/images/BasicManagedProfile@2x.png 2x"
+ alt="" height="375" />
+ <p class="img-caption">
+ <strong>Figure 1.</strong> The BasicManagedProfile sample app.
+ </p>
+</div>
+
+<p>This sample demonstrates how to create a managed profile. You can also:</p>
+<ul>
+ <li>Enable or disable other apps, and set restrictions on them.</li>
+ <li>Configure intents to be forwarded between the primary account and the
+ managed profile.</li>
+ <li>Wipe all the data associated with the managed profile.</li>
+</ul>
+
+<p class="note"><strong>Note:</strong> There can be only one managed profile on
+ a device at a time.</p>
+
+<p><a href="http://github.com/googlesamples/android-BasicManagedProfile">Get it on GitHub</a></p>
+
+<h3 id="Camera2Basic">Camera2Basic</h3>
+
+<!--
+<div class="figure" style="width:220px">
+ <img src="" srcset="@2x.png 2x" alt="" height="375" />
+ <p class="img-caption">
+ <strong>Figure n.</strong> Single sentence summarizing the figure.
+ </p>
+</div>
+-->
+
+<p>This sample demonstrates the basic use of the Camera2 API. The sample code
+demonstrates how you can display camera preview and take pictures.</p>
+
+<p><a href="http://github.com/googlesamples/android-Camera2Basic">Get it on GitHub</a></p>
+
+
+<h3 id="Camera2Video">Camera2Video</h3>
+<!--
+<div class="figure" style="width:220px">
+<img src="" srcset="@2x.png 2x" alt="" height="375" />
+ <p class="img-caption">
+ <strong>Figure n.</strong> Single sentence summarizing the figure.
+ </p>
+</div>
+-->
+
+<p>This sample demonstrates how to record video using the Camera2 API.</p>
+
+<p><a href="http://github.com/googlesamples/android-Camera2Video">Get it on GitHub</a></p>
+
+<h3 id="ActivitySceneTransitionBasic">ActivitySceneTransitionBasic</h3>
+<div class="figure" style="width:220px">
+ <img src="{@docRoot}preview/images/ActivitySceneTransitionBasic.png"
+ srcset="{@docRoot}preview/images/ActivitySceneTransitionBasic@2x.png 2x"
+ alt="" height="375" />
+ <p class="img-caption">
+ <strong>Figure 2.</strong> The ActivitySceneTransitionBasic sample app.
+ </p>
+ </div>
+
+<p> This sample demonstrates how to the use {@link android.app.Activity} scene
+transitions when transitioning from one activity to another. Uses a combination
+of <code>moveImage</code> and <code>changeBounds</code> to nicely transition
+from a grid of images to an activity with a large image and detail text. </p>
+
+<p><a href="http://github.com/googlesamples/android-ActivitySceneTransition">Get it on GitHub</a></p>
+
+<h3 id="ElevationBasic">ElevationBasic</h3>
+<!--
+<div class="figure" style="width:220px">
+<img src="" srcset="@2x.png 2x" alt="" height="375" />
+ <p class="img-caption">
+ <strong>Figure n.</strong> Single sentence summarizing the figure.
+ </p>
+</div>
+-->
+
+<p>
+This sample demonstrates two alternative ways to move a view in the z-axis:</p>
+
+<ul>
+ <li>With a fixed elevation, using XML.</li>
+ <li>Raising the elevation when the user taps on it, using
+ <code>setTranslationZ()</code>.</li>
+</ul>
+
+<p><a href="http://github.com/googlesamples/android-ElevationBasic">Get it on GitHub</a></p>
+
+<h3 id="ElevationDrag">ElevationDrag</h3>
+<!--
+<div class="figure" style="width:220px">
+ <img src="" srcset="@2x.png 2x" alt="" height="375" />
+ <p class="img-caption">
+ <strong>Figure n.</strong> Single sentence summarizing the figure.
+ </p>
+</div>
+-->
+
+<p>This sample demonstrates a drag and drop action on different shapes.
+Elevation and z-translation are used to render the shadows. The views are
+clipped using different outlines.</p>
+
+<p><a href="http://github.com/googlesamples/android-ElevationDrag">Get it on GitHub</a></p>
+
+
+<h3 id="ClippingBasic">ClippingBasic</h3>
+<!--
+<div class="figure" style="width:220px">
+ <img src="" srcset="@2x.png 2x" alt="" height="375" />
+ <p class="img-caption">
+ <strong>Figure n.</strong> Single sentence summarizing the figure.
+ </p>
+</div>
+-->
+
+<p>
+This sample demonstrates clipping on a {@link android.view.View}.
+</p>
+
+<p><a href="http://github.com/googlesamples/android-ClippingBasic">Get it on GitHub</a></p>
+
+<div class="figure" style="width:220px">
+ <img src="{@docRoot}preview/images/JobSchedulerSample.png"
+ srcset="{@docRoot}preview/images/JobSchedulerSample@2x.png 2x"
+ alt="" height="375" />
+ <p class="img-caption">
+ <strong>Figure 3.</strong> The JobSchedulerSample sample app.
+ </p>
+</div>
+
+<h3 id="GameControllerSample">GameControllerSample</h3>
+<!--
+<div class="figure" style="width:220px">
+ <img src="" srcset="@2x.png 2x" alt="" height="375" />
+ <p class="img-caption">
+ <strong>Figure n.</strong> Single sentence summarizing the figure.
+ </p>
+</div>
+-->
+
+<p>
+This sample implements a multi-player game, demonstrating game controller input
+handling.
+</p>
+
+<p><a href="http://github.com/googlesamples/androidtv-GameController">Get it on GitHub</a></p>
+
+
+<h3 id="Visual-Game-Controller">Visual-Game-Controller</h3>
+<!--
+<div class="figure" style="width:220px">
+ <img src="" srcset="@2x.png 2x" alt="" height="375" />
+ <p class="img-caption">
+ <strong>Figure n.</strong> Single sentence summarizing the figure.
+ </p>
+</div>
+-->
+
+<p>
+This sample displays events received from a game controller shown on the screen.
+</p>
+
+<p><a href="http://github.com/googlesamples/androidtv-VisualGameController">Get it on GitHub</a></p>
+
+<h3 id="AndroidTVLeanbackSample">AndroidTVLeanbackSample</h3>
+<!--
+<div class="figure" style="width:220px">
+ <img src="" srcset="@2x.png 2x" alt="" height="375" />
+ <p class="img-caption">
+ <strong>Figure n.</strong> Single sentence summarizing the figure.
+ </p>
+</div>
+-->
+
+<p>
+This sample demonstrates use of the Android TV Leanback Support Library.
+</p>
+
+<p><a href="http://github.com/googlesamples/androidtv-Leanback">Get it on GitHub</a></p>
+
+<h3 id="JobSchedulerSample">JobSchedulerSample</h3>
+
+<p>
+This sample app allows the user to schedule jobs through the UI, and shows
+visual cues when the jobs are executed.
+</p>
+
+<p><a href="http://github.com/googlesamples/android-JobScheduler">Get it on GitHub</a></p>
+
+<h3 id="NavigationDrawerSample">NavigationDrawerSample</h3>
+<!--
+<div class="figure" style="width:220px">
+ <img src="" srcset="@2x.png 2x" alt="" height="375" />
+ <p class="img-caption">
+ <strong>Figure n.</strong> Single sentence summarizing the figure.
+ </p>
+</div>
+-->
+
+<p>
+This sample illustrates a common usage of the Android support library's
+{@link android.support.v4.widget.DrawerLayout} widget.
+</p>
+
+<p><a href="http://github.com/googlesamples/android-NavigationDrawer">Get it on GitHub</a></p>
+<!--
+<h3 id="">SampleName</h3>
+
+<div class="figure" style="width:220px">
+ <img src="" srcset="@2x.png 2x" alt="" height="375" />
+ <p class="img-caption">
+ <strong>Figure n.</strong> Single sentence summarizing the figure.
+ </p>
+</div>
+
+<p>
+**description**
+</p>
+-->
diff --git a/docs/html/preview/setup-sdk.jd b/docs/html/preview/setup-sdk.jd
index 66262dc..af466ab 100644
--- a/docs/html/preview/setup-sdk.jd
+++ b/docs/html/preview/setup-sdk.jd
@@ -1,13 +1,181 @@
page.title=Setting Up the Preview SDK
@jd:body
+
+
+
+
+
+
+<div style="position:relative; min-height:600px">
+
+ <div class="wrap" id="tos" style="position:absolute;display:none;width:inherit;">
+
+ <p class="sdk-terms-intro">Before installing the Android SDK, you must agree to the following terms and conditions.</p>
+
+ <h2 class="norule">Terms and Conditions</h2>
+ <div class="sdk-terms" onfocus="this.blur()" style="width:678px">
+This is the Android SDK Preview License Agreement (the “License Agreement”).
+
+1. Introduction
+
+1.1 The Android SDK Preview (referred to in the License Agreement as the “Preview” and specifically including the Android system files, packaged APIs, and Preview library files, if and when they are made available) is licensed to you subject to the terms of the License Agreement. The License Agreement forms a legally binding contract between you and Google in relation to your use of the Preview.
+
+1.2 "Android" means the Android software stack for devices, as made available under the Android Open Source Project, which is located at the following URL: http://source.android.com/, as updated from time to time.
+
+1.3 "Google" means Google Inc., a Delaware corporation with principal place of business at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States.
+
+2. Accepting the License Agreement
+
+2.1 In order to use the Preview, you must first agree to the License Agreement. You may not use the Preview if you do not accept the License Agreement.
+
+2.2 By clicking to accept and/or using the Preview, you hereby agree to the terms of the License Agreement.
+
+2.3 You may not use the Preview and may not accept the License Agreement if you are a person barred from receiving the Preview under the laws of the United States or other countries including the country in which you are resident or from which you use the Preview.
+
+2.4 If you will use the Preview internally within your company or organization you agree to be bound by the License Agreement on behalf of your employer or other entity, and you represent and warrant that you have full legal authority to bind your employer or such entity to the License Agreement. If you do not have the requisite authority, you may not accept the License Agreement or use the Preview on behalf of your employer or other entity.
+
+3. Preview License from Google
+
+3.1 Subject to the terms of the License Agreement, Google grants you a royalty-free, non-assignable, non-exclusive, non-sublicensable, limited, revocable license to use the Preview, personally or internally within your company or organization, solely to develop applications to run on the Android platform.
+
+3.2 You agree that Google or third parties owns all legal right, title and interest in and to the Preview, including any Intellectual Property Rights that subsist in the Preview. "Intellectual Property Rights" means any and all rights under patent law, copyright law, trade secret law, trademark law, and any and all other proprietary rights. Google reserves all rights not expressly granted to you.
+
+3.3 You may not use the Preview for any purpose not expressly permitted by the License Agreement. Except to the extent required by applicable third party licenses, you may not: (a) copy (except for backup purposes), modify, adapt, redistribute, decompile, reverse engineer, disassemble, or create derivative works of the Preview or any part of the Preview; or (b) load any part of the Preview onto a mobile handset or any other hardware device except a personal computer, combine any part of the Preview with other software, or distribute any software or device incorporating a part of the Preview.
+
+3.4 You agree that you will not take any actions that may cause or result in the fragmentation of Android, including but not limited to distributing, participating in the creation of, or promoting in any way a software development kit derived from the Preview.
+
+3.5 Use, reproduction and distribution of components of the Preview licensed under an open source software license are governed solely by the terms of that open source software license and not the License Agreement. You agree to remain a licensee in good standing in regard to such open source software licenses under all the rights granted and to refrain from any actions that may terminate, suspend, or breach such rights.
+
+3.6 You agree that the form and nature of the Preview that Google provides may change without prior notice to you and that future versions of the Preview may be incompatible with applications developed on previous versions of the Preview. You agree that Google may stop (permanently or temporarily) providing the Preview (or any features within the Preview) to you or to users generally at Google's sole discretion, without prior notice to you.
+
+3.7 Nothing in the License Agreement gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, or other distinctive brand features.
+
+3.8 You agree that you will not remove, obscure, or alter any proprietary rights notices (including copyright and trademark notices) that may be affixed to or contained within the Preview.
+
+4. Use of the Preview by You
+
+4.1 Google agrees that nothing in the License Agreement gives Google any right, title or interest from you (or your licensors) under the License Agreement in or to any software applications that you develop using the Preview, including any intellectual property rights that subsist in those applications.
+
+4.2 You agree to use the Preview and write applications only for purposes that are permitted by (a) the License Agreement, and (b) any applicable law, regulation or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries).
+
+4.3 You agree that if you use the Preview to develop applications, you will protect the privacy and legal rights of users. If users provide you with user names, passwords, or other login information or personal information, you must make the users aware that the information will be available to your application, and you must provide legally adequate privacy notice and protection for those users. If your application stores personal or sensitive information provided by users, it must do so securely. If users provide you with Google Account information, your application may only use that information to access the user's Google Account when, and for the limited purposes for which, each user has given you permission to do so.
+
+4.4 You agree that you will not engage in any activity with the Preview, including the development or distribution of an application, that interferes with, disrupts, damages, or accesses in an unauthorized manner the servers, networks, or other properties or services of Google or any third party.
+
+4.5 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any data, content, or resources that you create, transmit or display through Android and/or applications for Android, and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so.
+
+4.6 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any breach of your obligations under the License Agreement, any applicable third party contract or Terms of Service, or any applicable law or regulation, and for the consequences (including any loss or damage which Google or any third party may suffer) of any such breach.
+
+4.7 The Preview is in development, and your testing and feedback are an important part of the development process. By using the Preview, you acknowledge that implementation of some features are still under development and that you should not rely on the Preview having the full functionality of a stable release. You agree not to publicly distribute or ship any application using this Preview as this Preview will no longer be supported after the official Android SDK is released.
+
+5. Your Developer Credentials
+
+5.1 You agree that you are responsible for maintaining the confidentiality of any developer credentials that may be issued to you by Google or which you may choose yourself and that you will be solely responsible for all applications that are developed under your developer credentials.
+
+6. Privacy and Information
+
+6.1 In order to continually innovate and improve the Preview, Google may collect certain usage statistics from the software including but not limited to a unique identifier, associated IP address, version number of the software, and information on which tools and/or services in the Preview are being used and how they are being used. Before any of this information is collected, the Preview will notify you and seek your consent. If you withhold consent, the information will not be collected.
+
+6.2 The data collected is examined in the aggregate to improve the Preview and is maintained in accordance with Google's Privacy Policy located at http://www.google.com/policies/privacy/.
+
+7. Third Party Applications
+
+7.1 If you use the Preview to run applications developed by a third party or that access data, content or resources provided by a third party, you agree that Google is not responsible for those applications, data, content, or resources. You understand that all data, content or resources which you may access through such third party applications are the sole responsibility of the person from which they originated and that Google is not liable for any loss or damage that you may experience as a result of the use or access of any of those third party applications, data, content, or resources.
+
+7.2 You should be aware the data, content, and resources presented to you through such a third party application may be protected by intellectual property rights which are owned by the providers (or by other persons or companies on their behalf). You may not modify, rent, lease, loan, sell, distribute or create derivative works based on these data, content, or resources (either in whole or in part) unless you have been specifically given permission to do so by the relevant owners.
+
+7.3 You acknowledge that your use of such third party applications, data, content, or resources may be subject to separate terms between you and the relevant third party.
+
+8. Using Google APIs
+
+8.1 Google APIs
+
+8.1.1 If you use any API to retrieve data from Google, you acknowledge that the data may be protected by intellectual property rights which are owned by Google or those parties that provide the data (or by other persons or companies on their behalf). Your use of any such API may be subject to additional Terms of Service. You may not modify, rent, lease, loan, sell, distribute or create derivative works based on this data (either in whole or in part) unless allowed by the relevant Terms of Service.
+
+8.1.2 If you use any API to retrieve a user's data from Google, you acknowledge and agree that you shall retrieve data only with the user's explicit consent and only when, and for the limited purposes for which, the user has given you permission to do so.
+
+9. Terminating the License Agreement
+
+9.1 the License Agreement will continue to apply until terminated by either you or Google as set out below.
+
+9.2 If you want to terminate the License Agreement, you may do so by ceasing your use of the Preview and any relevant developer credentials.
+
+9.3 Google may at any time, terminate the License Agreement, with or without cause, upon notice to you.
+
+9.4 The License Agreement will automatically terminate without notice or other action upon the earlier of:
+(A) when Google ceases to provide the Preview or certain parts of the Preview to users in the country in which you are resident or from which you use the service; and
+(B) Google issues a final release version of the Android SDK.
+
+9.5 When the License Agreement is terminated, the license granted to you in the License Agreement will terminate, you will immediately cease all use of the Preview, and the provisions of paragraphs 10, 11, 12 and 14 shall survive indefinitely.
+
+10. DISCLAIMERS
+
+10.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE PREVIEW IS AT YOUR SOLE RISK AND THAT THE PREVIEW IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND FROM GOOGLE.
+
+10.2 YOUR USE OF THE PREVIEW AND ANY MATERIAL DOWNLOADED OR OTHERWISE OBTAINED THROUGH THE USE OF THE PREVIEW IS AT YOUR OWN DISCRETION AND RISK AND YOU ARE SOLELY RESPONSIBLE FOR ANY DAMAGE TO YOUR COMPUTER SYSTEM OR OTHER DEVICE OR LOSS OF DATA THAT RESULTS FROM SUCH USE. WITHOUT LIMITING THE FOREGOING, YOU UNDERSTAND THAT THE PREVIEW IS NOT A STABLE RELEASE AND MAY CONTAIN ERRORS, DEFECTS AND SECURITY VULNERABILITIES THAT CAN RESULT IN SIGNIFICANT DAMAGE, INCLUDING THE COMPLETE, IRRECOVERABLE LOSS OF USE OF YOUR COMPUTER SYSTEM OR OTHER DEVICE.
+
+10.3 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
+
+11. LIMITATION OF LIABILITY
+
+11.1 YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU UNDER ANY THEORY OF LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR EXEMPLARY DAMAGES THAT MAY BE INCURRED BY YOU, INCLUDING ANY LOSS OF DATA, WHETHER OR NOT GOOGLE OR ITS REPRESENTATIVES HAVE BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING.
+
+12. Indemnification
+
+12.1 To the maximum extent permitted by law, you agree to defend, indemnify and hold harmless Google, its affiliates and their respective directors, officers, employees and agents from and against any and all claims, actions, suits or proceedings, as well as any and all losses, liabilities, damages, costs and expenses (including reasonable attorneys’ fees) arising out of or accruing from (a) your use of the Preview, (b) any application you develop on the Preview that infringes any Intellectual Property Rights of any person or defames any person or violates their rights of publicity or privacy, and (c) any non-compliance by you of the License Agreement.
+
+13. Changes to the License Agreement
+
+13.1 Google may make changes to the License Agreement as it distributes new versions of the Preview. When these changes are made, Google will make a new version of the License Agreement available on the website where the Preview is made available.
+
+14. General Legal Terms
+
+14.1 the License Agreement constitutes the whole legal agreement between you and Google and governs your use of the Preview (excluding any services which Google may provide to you under a separate written agreement), and completely replaces any prior agreements between you and Google in relation to the Preview.
+
+14.2 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in the License Agreement (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google.
+
+14.3 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of the License Agreement is invalid, then that provision will be removed from the License Agreement without affecting the rest of the License Agreement. The remaining provisions of the License Agreement will continue to be valid and enforceable.
+
+14.4 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to the License Agreement and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the License Agreement that confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to the License Agreement.
+
+14.5 EXPORT RESTRICTIONS. THE PREVIEW IS SUBJECT TO UNITED STATES EXPORT LAWS AND REGULATIONS. YOU MUST COMPLY WITH ALL DOMESTIC AND INTERNATIONAL EXPORT LAWS AND REGULATIONS THAT APPLY TO THE PREVIEW. THESE LAWS INCLUDE RESTRICTIONS ON DESTINATIONS, END USERS AND END USE.
+
+14.6 The License Agreement may not be assigned or transferred by you without the prior written approval of Google, and any attempted assignment without such approval will be void. You shall not delegate your responsibilities or obligations under the License Agreement without the prior written approval of Google.
+
+14.7 The License Agreement, and your relationship with Google under the License Agreement, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from the License Agreement. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction.
+ </div><!-- sdk terms -->
+
+
+
+ <div id="sdk-terms-form">
+ <p>
+ <input id="agree" type="checkbox" name="agree" value="1" onclick="onAgreeChecked()" />
+ <label id="agreeLabel" for="agree">I have read and agree with the above terms and conditions</label>
+ </p>
+ <p><a href="" class="button disabled" id="downloadForRealz" onclick="return onDownloadForRealz(this);"></a></p>
+ </div>
+
+
+ </div><!-- end TOS -->
+
+
+
+
+
+
+
+
+
+ <div id="landing">
+
<p>The Preview SDK is available from the Android SDK Manager. <!-- Not yet! -->
This document assumes that you are familiar with Android app development, such
as using the Android SDK Manager and creating projects. If you're new to
Android, see <a href="/training/basics/firstapp/index.html">Building Your First
App</a> training lesson first.</a></p>
-<h2>Download the SDK</h2>
+<h2 id="downloadSdk">Download the SDK</h2>
<ol>
<li>Start the Android SDK Manager.</li>
@@ -20,7 +188,10 @@
<b>Install</b>.</li>
</ol>
-<h2>Set Up Hardware and AVDs</h2>
+<p class="note"><strong>Note:</strong> The Eclipse ADT plug-in requires Java 7
+if your compilation target is the L developer preview.</p>
+
+<h2 id="setupHardware">Set Up Hardware and AVDs</h2>
<p>The Android L developer preview provides you with 32-bit system images
to flash the following devices:
@@ -35,10 +206,9 @@
experimental 64-bit system images along with standard 32-bit system images.
</p>
-<h3>Instal the L Preview System Image</h3>
+<h3 id="installImage">Install the L Preview System Image</h3>
-<!-- Will we get an official warning text from the lawyercats? Is this it? -->
-<p class="warning"><b>Warning</b>: This is a preview version of the Android
+<p class="warning"><b>Warning:</b> This is a preview version of the Android
system image, and is subject to change. Your use of this system image is
governed by the Android SDK Preview License Agreement. The Android preview
system image is not a stable release, and may contain errors and defects that
@@ -49,35 +219,36 @@
<ol>
- <li>Download and extract the Android Developer Preview package to a directory
- (which we'll call <code><l_download_dir></code> in these
- instructions).</li>
- <li>Connect your powered-off Android device to your development machine. Put
- the device in fastboot mode by pressing and holding the following buttons:
- <ul>
- <li><strong>Nexus 5:</strong> <i>volume down</i> + <i>volume up</i> +
- <i>power</i></li>
- <li><strong>Nexus 7:</strong> <i>volume down</i> + <i>power</i> </li>
- </ul>
- <p class="note">Alternatively, you can enter fastboot mode by booting up
- the device and running <code>adb reboot bootloader</code> with USB debugging
- turned on.</p>
+ <li>Download and uncompress the Android Developer Preview package.
+ <table style="width:860px">
+ <tr>
+ <th scope="col">Device</th>
+ <th scope="col">Download</th>
+ <th scope="col">Checksum</th>
+ </tr>
+ <tr id="hammerhead">
+ <td>Nexus 5 (GSM/LTE) <br>"hammerhead"</td>
+ <td><a href="#top" onclick="onDownload(this)"
+ >hammerhead-lpv79-preview-ac1d8a8e.tgz</a></td>
+ <td>MD5: <code>5a6ae77217978cb7b958a240c2e80b57</code>
+ <br>SHA-1: <code>ac1d8a8e4f4a1dca5864dc733caa940bffc28616</code></td>
+ </tr>
+ <tr id="razor">
+ <td>Nexus 7 (Wifi) <br>"razor"</td>
+ <td><a href="#top" onclick="onDownload(this)"
+ >razor-lpv79-preview-d0ddf8ce.tgz</a></td>
+ <td>MD5: <code>b293a5d3a4e07beabebcc0be85ad68a2</code>
+ <br><nobr>SHA-1: <code>d0ddf8ce733ba2a34279cdff8827fd604762c2342d</nobr></td>
+ </tr>
+ </table>
</li>
+
<li>Follow the instructions at
<a href="https://developers.google.com/android/nexus/images#instructions">developers.google.com/android</a>
- to set up your system for flashing devices.</li>
- <li>Run the <code><l_download_dir>/flash-all</code> script
- corresponding to your platform. This script flashes all of the system data
- onto the phone.</li> <!-- Confirm names of flash scripts -->
- <li>(Optional) After flashing is complete, lock your device's bootloader by
- putting it in fastboot mode and running <code>fastboot oem lock</code>.
- (This does not wipe your device.) Once you do this, you will not be able to
- flash your device until you run run <code>fastboot oem unlock</code>,
- which unlocks the bootloader and wipes your device. We recommend you leave the
- bootloader unlocked until you are done with flashing the device.</li>
+ to flash the image onto your device.</li>
</ol>
-<h3>Revert a Device to Factory Specifications</h3>
+<h3 id="revertDevice">Revert a Device to Factory Specifications</h3>
<p>If you want to uninstall the L Preview and revert the device to factory
specifications, go to <a href="http://developers.google.com/android
@@ -85,8 +256,7 @@
to flash to for your device. Follow the instructions on that page to flash the
image to your device.</p>
-
-<h3>Set up an AVD</h3>
+<h3 id="setupAVD">Set up an AVD</h3>
<p>You can set up <a href="{@docRoot}tools/devices/">Android Virtual Devices
(AVD)</a> and use the emulator to build and test apps with the L Preview.</p>
@@ -109,17 +279,86 @@
</li>
</ol>
-<h2>Create a Project</h2>
+<h2 id="createProject">Create a Project</h2>
-<ol>
- <li>Create a new Android project with the following properties:
- <ul>
- <li>Minimum SDK Version: L</li>
- <li>Target SDK Version: L</li>
- <li>Build Target: L</li>
- </ul>
- </li>
- <li>Choose the theme <code>Theme.Material</code>
- <!-- put in name as it appears in Eclipse menu? -->
+<p>Android Studio makes it easy to create a project for the L Developer Preview. Follow
+the steps described in <a href="{@docRoot}sdk/installing/create-project.html">Creating a
+Project</a>. In the <strong>Form Factors</strong> screen:</p>
-</ol>
+<ul>
+ <li>Check <strong>Phone and Tablet</strong>.</li>
+ <li>Select <strong>API 20+: Android L (Preview)</strong> in <strong>Minimum SDK</strong>.</li>
+</ul>
+
+<p>On the development environment, open the <code>build.gradle</code> file for your module
+and make sure that:</p>
+
+<ul>
+ <li><code>compileSdkVersion</code> is set to <code>'android-L'</code></li>
+ <li><code>minSdkVersion</code> is set to <code>'L'</code></li>
+ <li><code>targetSdkVersion</code> is set to <code>'L'</code></li>
+</ul>
+
+<p>To use the material theme, open the <code>values/styles.xml</code> in your project and make
+sure that you theme extends the material theme:</p>
+
+<pre>
+<resources>
+ <style name="AppTheme" parent="android:Theme.Material">
+ <!-- Customize your theme here -->
+ </style>
+</resources>
+</pre>
+
+ </div><!-- landing -->
+
+</div><!-- relative wrapper -->
+
+
+
+<script>
+ var urlRoot = "http://storage.googleapis.com/androiddevelopers/preview/";
+ function onDownload(link) {
+
+ $("#downloadForRealz").html("Download " + $(link).text());
+ $("#downloadForRealz").attr('href', urlRoot + $(link).text());
+
+ $("#tos").fadeIn('fast');
+ $("#landing").fadeOut('fast');
+
+ return true;
+ }
+
+
+ function onAgreeChecked() {
+ /* verify that the TOS is agreed */
+ if ($("input#agree").is(":checked")) {
+ /* reveal the download button */
+ $("a#downloadForRealz").removeClass('disabled');
+ } else {
+ $("a#downloadForRealz").addClass('disabled');
+ }
+ }
+
+ function onDownloadForRealz(link) {
+ if ($("input#agree").is(':checked')) {
+ $("#tos").fadeOut('fast');
+ $("#landing").fadeIn('fast');
+ _gaq.push(['_trackEvent', 'L Preview', 'System Image', $("#downloadForRealz").html()]);
+ location.hash = "";
+ return true;
+ } else {
+ $("label#agreeLabel").parent().stop().animate({color: "#258AAF"}, 200,
+ function() {$("label#agreeLabel").parent().stop().animate({color: "#222"}, 200)}
+ );
+ return false;
+ }
+ }
+
+ $(window).hashchange( function(){
+ if (location.hash == "") {
+ location.reload();
+ }
+ });
+
+</script>
\ No newline at end of file
diff --git a/docs/html/preview/support.jd b/docs/html/preview/support.jd
index 9ab25dd..9d7844b 100644
--- a/docs/html/preview/support.jd
+++ b/docs/html/preview/support.jd
@@ -7,7 +7,7 @@
our issue tracker.</p>
<p>For more support,
-<a href="https://plus.google.com/communities/113159138894928487684">join
+<a href="https://plus.google.com/communities/101985907812750684586">join
the L Developer Preview Google+ community</a> to discuss your development experiences.
@@ -34,7 +34,7 @@
<li>On very tall or wide views, view shadows may appear with additional rough
visual artifacts around the view edges. To minimize this, avoid using view
shadows with very narrow views.</li>
-<li>The {@code android.graphics.drawable.RippableDrawable} class does not
+<li>The {@code android.graphics.drawable.RippleDrawable} class does not
respond to pointer location changes, except when the drawable is set as a
{@link android.view.View} background.</li>
</ul>
@@ -54,11 +54,12 @@
stillCaptureRequest.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE,
CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE_ON);
</pre></li>
-<li>Portions of the internal audio pipeline do not support floating-point format.</li>
<li>The {@code android.media.AudioTrack.write(float[], int, int, int)} method
currently does not work. Use the
{@link android.media.AudioTrack#write(short[], int, int)
AudioTrack.write(short[], int, int)} method instead.</li>
+<li>Lockscreen security is currently not enforced when users start a Android
+mirroring session from the Quick Settings shade.</li>
</ul>
<h3 id="UserInput">User input</h3>
@@ -66,21 +67,42 @@
the locale is set to {@code fr} (FRENCH).</li></ul>
<h3 id="Wireless">Wireless and Connectivity</h3>
-<ul><li>The {@code android.bluetooth.le} APIs are supported only on Nexus 5
-devices.</li></ul>
+<ul>
+<li>The {@code android.bluetooth.le} APIs are supported only on Nexus 5
+devices.</li>
+<li>You might encounter these issues while using Bluetooth LE scanning:
+ <ul>
+ <li><em>Settings</em> does not show all Bluetooth LE devices when a scan
+ filter is set.</li>
+ <li>System returns non-intuitive error messages during a Bluetooth LE scan,
+ when Bluetooth is off.</li>
+ <li>The {@code BluetoothLeScanner.startScan()} method starts failing after
+ six concurrent scans with different callbacks.</li>
+ </ul>
+</li>
+<li>You might encounter these issues while using Bluetooth LE advertising:
+ <ul>
+ <li>The device MAC address does not change for multiple advertising
+ when the application processor is asleep.</li>
+ <li>The TX Power Level is always 0 in advertising packets.</li>
+ </ul>
+</li>
+</ul>
<h3 id="Enterprise">Enterprise</h3>
<ul>
<li>The device may crash unexpectedly in these situations when using
-Android for Work functionality:
+Android work functionality:
<ul>
-<li>The user attempts to share a webpage (via <strong>Menu > Share</strong>)
-from a non-managed Chrome app to a managed Gmail app.</li>
-<li>The user attempts to share a webpage via Bluetooth from a managed Chrome app.</li>
-<li>The user attempts to share a webpage via Android Beam from a managed Chrome app.</li>
+<li>The user attempts to share a web page (via <strong>Menu > Share</strong>)
+from a non-Android work Chrome app to a Android work profile Gmail app.</li>
+<li>The user attempts to share a web page via Bluetooth from a
+Android work profile
+Chrome app.</li>
+<li>The user attempts to share a web page via Android Beam from a
+Android work profile Chrome app.</li>
</ul>
</li>
-<li>Deleting the managed work profile (profile owner) may take several minutes
-to complete. You cannot create a new managed profile until the deletion
-operation is over.</li>
+<li>Deleting a Android work profile may take several minutes to complete. You
+cannot create a new Android work profile until the deletion operation is over.</li>
</ul>
diff --git a/docs/html/preview/tv/adt-1/index.jd b/docs/html/preview/tv/adt-1/index.jd
new file mode 100644
index 0000000..d83dd11
--- /dev/null
+++ b/docs/html/preview/tv/adt-1/index.jd
@@ -0,0 +1,282 @@
+page.title=ADT-1 Developer Kit
+page.tags="emote","e-mote","adt"
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#faq">Frequently Asked Questions</a>
+ <ol>
+ <li><a href="#setup">Device Setup</a></li>
+ <li><a href="#input">User Input</a></li>
+ <li><a href="#cast">Google Cast</a></li>
+ <li><a href="#trouble">Troubleshooting</a></li>
+ </ol>
+ </li>
+ <li><a href="#emote">Android TV Remote Control App</a></li>
+ <li><a href="#reg-safety">Regulatory Disclosures and Safety</a></li>
+ </ol>
+</div>
+</div>
+
+<p>The ADT-1 Developer Kit is a streaming media player and game controller designed for running
+and testing apps built for Android TV. Supplies of ADT-1 are limited and it is intended for
+developers who are interested in building new apps or extending their existing apps to run on the
+Android TV platform.</p>
+
+<p class="note">
+ <strong>Note:</strong> The ADT-1 kit <em>is not required</em> for building and testing apps
+ for Android TV. You can build apps for TV and test them using an emulator for TV devices. The
+ L Developer Preview includes all the software needed to build TV apps and an emulator for running
+ and testing them. For more information, see the
+ <a href="{@docRoot}preview/tv/start/index.html">Get Started</a> guide for TV apps.
+</p>
+
+<h2 id="faq">ADT-1 Frequently Asked Questions</h2>
+
+<p>The following information is provided to help set up and use the ADT-1 device.</p>
+
+
+<h3 id="setup">Device Setup</h3>
+
+<p>
+ <strong>How do I turn my device on?</strong>
+</p>
+<p>Plug the included power cable into the back of ADT-1. The device does not have an on/off
+ switch.</p>
+
+<p>
+ <strong>How do I completely turn my device off? </strong>
+</p>
+<p>Unplug the included power cable from the back of ADT-1. The device does not have an on/off
+ switch. However, ADT-1 will begin sleeping (daydream) based on user settings in
+ <strong>Settings > Display > Daydream</strong>.
+ </p>
+
+<p>
+ <strong>How do I connect to the network?</strong>
+</p>
+<p>ADT-1 has both wireless and Ethernet for connecting to your network. To change your wireless
+ network, go to <strong>Settings -> Wi-Fi</strong>. To use an Ethernet network connection,
+ simply plug an Ethernet cable (that is connected to your network) into the port on the back of
+ ADT-1.</p>
+
+<p>
+ <strong>How do I use the developer cable?</strong>
+</p>
+<p>The developer cable has three connectors: a small, male power connector that plugs into the
+ power port on the back of ADT-1, a standard male USB-A connector that connects your PC, and a
+ small, female power connector that the included power supply plugs into.</p>
+
+
+
+<h3 id="input">User Input</h3>
+
+<p>
+ <strong>How do I put the gamepad that came with my ADT-1 into pairing mode?</strong>
+</p>
+<p>Press and hold the Back and Home buttons together for about three seconds, until all four
+ blue LEDs flash together. When the LEDs are flashing, the gamepad is in pairing mode.</p>
+
+<p>
+ <strong>How do I use the gamepad with the on-screen keyboard?</strong>
+</p>
+<p>Use the D-pad or left joystick to move the cursor, and press A to select. Press X to delete a
+ character, and press Y to insert a space. Also, you can press the right joystick to toggle caps
+ lock, and press the left joystick to show additional symbols.</p>
+
+<p>
+ <strong>Can I control ADT-1 with my phone or tablet?</strong>
+</p>
+<p>Yes. In order to control the ADT-1 with Android phones or tablets, you can download a remote
+ control app from the Google Play store. For more information, see <a href="#emote">Android TV
+ Remote Control App</a>.
+ </p>
+
+<p>
+ <strong>Can I connect a USB keyboard or mouse to ADT-1?</strong>
+</p>
+<p>Yes, you can connect a USB keyboard or mouse to the USB port on the back of ADT-1.
+
+<p class="note">
+ <strong>Note:</strong> The ADT-1 device is not compatible with all manufacturers and models of
+ these devices. If a particular keyboard or mouse does not work, try a different model.
+</p>
+
+<p>
+ <strong>How do I connect a Bluetooth device without an input device already attached?</strong>
+</p>
+<p>You can put ADT-1 into Bluetooth pairing mode using a hardware button. Press the small, round
+ button on the back of ADT-1 to make it search for Bluetooth devices in pairing mode. If multiple
+ accessories are found, press the small, round button to select the device you want to pair.
+ Pairing will happen automatically after a few seconds.
+</p>
+
+<p>
+ <strong>How do I connect additional Bluetooth accessories?</strong>
+<p>
+<p>To pair Bluetooth devices to ADT-1 from the user interface, go to <strong>Settings >
+ Remote & Accessories > Add accessory</strong>
+
+
+<h3 id="cast">Google Cast</h3>
+
+<p>
+ <strong>Can I cast to an ADT-1 device?</strong>
+<p>
+<p>Yes. The ADT-1 includes Google Cast receiver functionality, similar to Chromecast. Since the
+ ADT-1 is a developer device running a development software release, the Google Cast receiver is
+ open only to a limited number of apps.</p>
+
+<p>
+ <strong>Which Cast apps are supported on ADT-1?</strong>
+<p>
+<p>As a developer device, the ADT-1 supports casting from only the following apps/websites:</p>
+
+<ul>
+ <li>YouTube</li>
+ <li>Netflix</li>
+ <li>Google+ Photos</li>
+ <li>Google Play Movies and TV (Android only)</li>
+</ul>
+
+<p>Coming soon:</p>
+
+<ul>
+ <li>Google Play Music</li>
+ <li>Google Play Movies and TV (iOS and Chrome)</li>
+ <li>Mirror you Android device screen to ADT-1</li>
+</ul>
+
+<p class="note">
+ <strong>Note:</strong> When casting from a Chrome browser, you must use Chrome V.36 or higher.
+ Chrome V.36 is available in beta-channel and is planned to be released soon.
+</p>
+
+<p>
+ <strong>How do I cast to ADT-1?</strong>
+<p>
+<p>You cast to an ADT-1 device the same way you do with a Chromecast device. Open the supported
+ Cast apps or webpages, press the <strong>Cast</strong> button and you should see the ADT-1 as a
+ Cast target. For more infomation about on how to cast, see
+ <a href="http://www.google.com/intl/en/chrome/devices/chromecast/learn.html">Learn How to
+ Cast</a>.
+ </p>
+
+<p>
+ <strong>Will my Google Cast sender apps work on ADT-1 just like Chromecast?</strong>
+<p>
+<p>Yes. Your Cast app works on ADT-1 and Android TV products without additional work.<p>
+
+<p class="note">
+ <strong>Note:</strong> Your iOS sender app requires the Google Cast iOS API version 2.2.1
+ or later to work with the ADT-1 device.
+</p>
+
+<p>
+ <strong>How do I register my ADT-1 in order to run my apps?</strong>
+</p>
+<ol>
+ <li>Go to <strong>Settings > Google Cast</strong> and turn on developer support, allowing the
+ ADT-1 device to send its serial number to Google.</li>
+ <li>Register your ADT-1 device in the Google Cast Developer Console, using the 12 character
+ serial number engraved on the back of the ADT-1.</li>
+</ol>
+
+<p>For more Google Cast developer information, see the
+ <a href="https://developers.google.com/cast/">Cast developer site</a>. Please use the Google Cast
+ SDK <a href="https://code.google.com/p/google-cast-sdk/issues/list">issue tracker</a> for filing
+ issues related to Cast. Make sure you mention the ADT-1 device when filing an issue.
+</p>
+
+<p>
+ <strong>How do I debug my Cast app on ADT-1?</strong>
+</p>
+<p>Connect your development platform using the power/USB cable, and using a Chrome browser,
+ navigate to <code>chrome://inspect/#devices</code> to debug the webview.</p>
+
+
+<h3 id="trouble">Troubleshooting</h3>
+
+<p>
+ <strong>Why doesn't the on-screen keyboard come up?</strong>
+</p>
+<p>Enable the keyboard in the device Settings. Go to <strong>Settings > Keyboard > Current
+ keyboard</strong> and choose <strong>Leanback keyboard</strong>.
+
+<p>
+ <strong>How do I perform a hardware reboot?</strong>
+</p>
+<p>Locked it up, huh? No worries. We've done that a few times ourselves. Unplug and replug the
+ included power cable from the back of ADT-1 to reboot it.
+</p>
+
+<p>
+ <strong>How do I perform a factory reset?</strong>
+</p>
+<p class="warning">
+ <strong>Warning:</strong> This procedure removes all data from the device, including system
+ data, downloaded apps, app data, and account settings.
+</p>
+
+<p>From the home screen, go to <strong>Settings > Device > Factory data reset</strong>, and
+ select <strong>Reset device</strong>.
+</p>
+
+<p>
+ <strong>How do I perform a hardware reset?</strong>
+</p>
+<p class="warning">
+ <strong>Warning:</strong> This procedure performs a factory data reset, removing all data from
+ the device, including system data, downloaded apps, app data, and account settings.
+</p>
+
+<p>Unplug the power cable from the back of ADT-1. Press and hold the small, round button on the
+ back of ADT-1 as you re-insert the power cable, and continue to hold the small round button. The
+ LED will begin flashing red for a few seconds, then change to multi-color cycle. When the LED
+ starts the multi-color cycle, release the small, round button, and ADT-1 boots up. If you release
+ the button while the LED is flashing red, the device will be in Fastboot mode.</p>
+
+<p>
+ <strong>There is a hardware problem with my ADT-1. How do I return it?</strong>
+</p>
+<p>You can request a return of the device using the
+ <a href="https://support.google.com/googleplay/android-developer/contact/adt_rma">return
+ merchandise authorization form</a>.
+</p>
+
+
+<h2 id="emote">Android TV Remote Control App</h2>
+
+<div class="figure" style="width:250px;margin-top:0">
+<img src="/preview/tv/images/android-tv-remote.png" alt="Android TV Remote Screenshots">
+</div>
+
+<p>A remote control app is available for Android phones and tablets that allows you to interact
+ with the ADT-1 device. This app allows you to switch between D-pad input mode or touchpad mode
+ to navigate content and play games on a Android TV device. You can also tap the mic button to
+ start a voice search, or use the keyboard to input text using this app.</p>
+
+<p>You download the remote control app from the Google Play store using
+ <a href="https://play.google.com/store/apps/details?id=com.google.android.tv.remote">this
+ link</a>.
+</p>
+
+<p class="note">
+ <strong>Note:</strong> your Android phone or tablet must be connected to the same local network
+ as ADT-1.
+</p>
+
+
+<h2 id="reg-safety">Regulatory Disclosures and Safety Information</h2>
+
+<p>The ADT-1 device comes with important regulatory disclosures and safety information. Please
+read this information before using the device:</p>
+
+<ul>
+ <li><a href="regulatory.html">Regulatory Disclosures</a></li>
+ <li><a href="safety.html">Important Safety Information</a></li>
+</ul>
+
diff --git a/docs/html/preview/tv/adt-1/regulatory.jd b/docs/html/preview/tv/adt-1/regulatory.jd
new file mode 100644
index 0000000..2f5bf7e
--- /dev/null
+++ b/docs/html/preview/tv/adt-1/regulatory.jd
@@ -0,0 +1,79 @@
+page.title=Regulatory Disclosures for ADT-1
+parent.title=ADT-1 Developer Kit
+parent.link=index.html
+
+@jd:body
+
+<p>Disclosures for the <a href="index.html">ADT-1</a> device.</p>
+
+<p>
+ Model: W2<br>
+ FCC ID: A4R-W2<br>
+ IC: 10395A-W2
+</p>
+
+<p>U.S. Federal Communications Commission Notices</p>
+<p>To satisfy FCC and IC exposure requirements, a separation distance of at least 20 cm should
+ be maintained between the antenna of this device and persons during device operation. Operations
+ at closer than this distance are not recommended.</p>
+<p>The antenna used for this transmitter must not be co-located in conjunction with any other
+ antenna or transmitter.</p>
+<p>This equipment has been tested and found to comply with the limits for a Class B digital
+ device, pursuant to part 15 of the FCC Rules. These limits are designed to provide reasonable
+ protection against harmful interference in a residential installation. This equipment generates,
+ uses and can radiate radio frequency energy and, if not installed and used in accordance with the
+ instructions, may cause harmful interference to radio communications. However, there is no
+ guarantee that interference will not occur in a particular installation. If this equipment does
+ cause harmful interference to radio or television reception, which can be determined by turning
+ the equipment off and on, the user is encouraged to try to correct the interference by one or more
+ of the following measures:</p>
+<p>—Reorient or relocate the receiving antenna.</p>
+<p>—Increase the separation between the equipment and receiver.</p>
+<p>—Connect the equipment into an outlet on a circuit different from that to which the receiver
+ is connected.</p>
+<p>—Consult the dealer or an experienced radio/ TV technician for help.</p>
+<p>This device complies with part 15 of the FCC Rules. Operation is subject to the following two
+ conditions: (1) This device may not cause harmful interference, and (2) this device must accept
+ any interference received, including interference that may cause undesired operation.</p>
+<p>Changes or modifications not expressly approved by Google Inc. could void the user's
+ authority to operate the equipment.</p>
+<p>Industry Canada Notices</p>
+<p>This device complies with Industry Canada licence-exempt RSS standard(s). Operation is
+ subject to the following two conditions: (1) this device may not cause interference, and (2) this
+ device must accept any interference, including interference that may cause undesired operation of
+ the device.</p>
+<p>Under Industry Canada regulations, this radio transmitter may only operate using an antenna
+ of a type and maximum (or lesser) gain approved for the transmitter by Industry Canada. To reduce
+ potential radio interference to other users, the antenna type and its gain should be so chosen
+ that the equivalent isotropically radiated power (e.i.r.p.) is not more than that necessary for
+ successful communication.</p>
+<p>The radiated output power of the Wireless Device is below the Industry Canada (IC) radio
+ frequency exposure limits. The Wireless Device should be used in such a manner such that the
+ potential for human contact during normal operation is minimized.</p>
+
+<hr />
+
+<p>CAN ICES-3 (B)/NMB-3(B)</p>
+<p>
+ <u>Avis d’<em>Industrie Canada</em></u>
+</p>
+<p>
+ Le présent appareil est conforme aux <em>CNR</em> d'Industrie Canada applicables aux appareils
+ radio exempts de licence. L'exploitation est autorisée aux deux conditions suivantes : (1)
+ l'appareil ne doit pas produire de brouillage, et (2) l'appareil doit accepter tout brouillage
+ radioélectrique subi, même si le brouillage est susceptible d'en compromettre le fonctionnement.
+</p>
+<p>
+ En vertu de la règlementation d’<em>Industrie Canada</em>, cet émetteur radio peut
+ fonctionner avec une antenne d'un type et d'un gain maximal (ou inférieur) approuvé pour
+ l'émetteur par <em>Industrie Canada</em>. Dans le but de réduire les risques de brouillage
+ radioélectrique à l'intention des autres utilisateurs, il faut choisir le type d'antenne et son
+ gain de sorte que la puissance isotrope rayonnée équivalente (p.i.r.e.) ne dépasse pas l'intensité
+ nécessaire à l'établissement d'une communication satisfaisante.
+</p>
+<p>
+ La puissance rayonnée en sortie de l'appareil sans fil est inférieure aux limites fixées par
+ <em>Industrie Canada</em> en matière d'exposition aux radiofréquences. L'appareil sans fil
+ doit être utilisé de sorte que la possibilité d'un contact humain pendant le fonctionnement
+ normal soit limitée.
+</p>
diff --git a/docs/html/preview/tv/adt-1/safety.jd b/docs/html/preview/tv/adt-1/safety.jd
new file mode 100644
index 0000000..1984853
--- /dev/null
+++ b/docs/html/preview/tv/adt-1/safety.jd
@@ -0,0 +1,140 @@
+page.title=Important Safety Instructions for ADT-1
+parent.title=ADT-1 Developer Kit
+parent.link=index.html
+
+@jd:body
+
+<p>Safety information for the <a href="index.html">ADT-1</a> device.</p>
+
+<p>
+ <strong>WARNING:</strong> Read all safety information below before using this device to avoid
+ injury.
+</p>
+<ul>
+ <li><p>Do not install near heat sources, such as heaters and other devices.</p></li>
+ <li><p>Use in a well-ventilated area and plug power adapter into an easily accessible
+ outlet. Only use this device with the provided power adapter.</p></li>
+ <li><p>The device has no on/off switch. To disconnect from power, you must unplug the
+ power adapter.</p></li>
+ <li><p>Only use indoors and do not expose to rain, liquid, moisture, excessive heat, or
+ naked flame.</p></li>
+ <li><p>Clean only with a dry cloth.</p></li>
+</ul>
+<p>
+ <strong>WARNING:</strong> Playing video games has been linked to injuries in some
+ users. Read all safety and health information below before using the gamepad to avoid possible
+ injury.
+</p>
+
+<p><u>Photosensitive Seizures</u></p>
+
+<p>
+ A very small percentage of people may experience a seizure when exposed to certain visual images,
+ including flashing lights or patterns that may appear in some video games, even people who have no
+ history of seizures or epilepsy. These seizures have a variety of symptoms, including
+ lightheadedness, altered vision, disorientation, loss of awareness, involuntary movements, loss of
+ consciousness, or convulsions. If you experience any of these symptoms, <u>stop gaming
+ immediately and consult your doctor</u>.
+</p>
+
+<p><u>Ergonomics</u></p>
+
+<p>Long periods of repetitive motion using incorrect body positioning may be associated with
+ physical discomfort and injuries to nerves, tendons, and muscles. If during or after gaming you
+ feel pain, numbness, weakness, swelling, burning, cramping, or stiffness, <u>stop gaming
+ and consult your doctor</u>.
+
+<p>
+ <strong>Healthy Gaming</strong>
+</p>
+
+<p>To reduce risk of seizures or injury, take the following precautions:</p>
+
+<ul>
+ <li><p>Sit as far away from the TV screen as possible.</p></li>
+ <li><p>Play in a well-lit room.</p></li>
+ <li><p>Do not play when you are drowsy or fatigued.</p></li>
+ <li><p>Take 10-15 minute breaks every hour if playing video games and avoid prolonged
+ gaming.</p></li>
+</ul>
+
+<p>
+ <strong>Do Not Attempt Repairs Yourself</strong>
+</p>
+
+<p>There are no user-serviceable parts inside. Do not attempt to open or disassemble.</p>
+
+<p>Failure to follow these safety instructions could result in fire, electric shock, damage to
+ the device or other property, or personal injury.</p>
+
+<hr />
+
+<p>
+ <strong>Importantes instructions concernant la sécurité</strong>
+</p>
+
+<p>
+ <strong>ATTENTION:</strong> Veuillez lire toutes les informations de sécurité énoncées ci-bas
+ avant d’utiliser l’appareil pour éviter des blessures.
+</p>
+
+<ul>
+ <li><p>Ne pas installer à proximité d’une source de chaleur telle une chaufferette ou un
+ autre appareil similaire.</p></li>
+ <li><p>Utiliser dans un endroit bien aéré et brancher l’adaptateur électrique dans une
+ prise de courant facilement accessible.</p></li>
+ <li><p>L’appareil ne possède aucun interrupteur marché/arrêt. Pour mettre l’appareil hors
+ tension, il faut débrancher l’appareil de la prise de courant.</p></li>
+ <li><p>Utiliser l’appareil uniquement à l’intérieur et ne pas l’exposer à la pluie, à des
+ substances liquides, à l’humidité, à la chaleur excessive ou à une flamme.</p></li>
+ <li><p>Nettoyer uniquement avec un linge sec.</p></li>
+</ul>
+
+<p>
+ <strong>ATTENTION:</strong> Le fait de jouer à des jeux vidéo a été relié à des blessures chez certains
+ utilisateurs. Afin d’éviter de possibles blessures, veuillez lire toutes les informations
+ concernant la sécurité et la santé énoncées ci-bas avant d’utiliser la tablette de jeu.
+</p>
+
+<p><u>Épilepsie photosensible</u></p>
+
+<p>L’exposition à certaines images visuelles, incluant les lumières ou motifs clignotants qui
+ peuvent apparaître dans certains jeux vidéo, peut provoquer chez un très faible pourcentage de
+ personnes une crise d’épilepsie, et ce, même si ces personnes n’ont aucun historique de crises ou
+ d’épilepsie. Ces crises comportent divers symptômes tels que des étourdissements, une vision
+ altérée, un sentiment de désorientation, la perte de conscience, des mouvements involontaires, la
+ perte de connaissance ou de conscience ou des convulsions. Si vous ressentez quelconque de ces
+ symptômes, <u>cessez de jouer immédiatement et consultez votre médecin</u>.</p>
+
+<p><u>Ergonomie</u></p>
+
+<p>Les longues périodes de mouvements répétitifs effectués dans une position corporelle
+ inadéquate peuvent mener à un inconfort physique et à des blessures aux nerfs, tendons et muscles.
+ Si durant ou après avoir joué à des jeux vidéo, vous ressentez de la douleur, de
+ l’engourdissement, une faiblesse, de l’inflammation, une sensation de brûlure, des crampes ou de
+ la rigidité, <u>cessez de jouer immédiatement et consultez votre médecin</u>.</p>
+
+<p>
+ <strong>Le jeu sécuritaire</strong>
+</p>
+
+<p>Afin de réduire les risques de crises d’épilepsie ou de blessures, veuillez prendre les
+ précautions suivantes :</p>
+
+<ul>
+ <li>Asseyez-vous aussi loin de l’écran de télévision que possible.</li>
+ <li>Jouez dans une pièce munie d’un éclairage adéquat.</li>
+ <li>Ne jouez pas lorsque vous êtes étourdi ou fatigué.</li>
+ <li>Prenez 10 à 15 minutes de pause après chaque heure de jeu et évitez les périodes de jeu
+ prolongées.</li>
+</ul>
+
+<p>
+ <strong>Ne pas tenter d’effectuer des réparations par vous-même</strong>
+</p>
+
+<p>L’Appareil ne contient aucune pièce pouvant être réparée par l’utilisateur. Ne pas tenter
+ d’ouvrir ou de désassembler l’Appareil.</p>
+
+<p>Le défaut de suivre ces instructions de sécurité pourrait provoquer un feu, un choc
+ électrique, un dommage à l’Appareil ou à d’autres objets ou des lésions corporelles.</p>
diff --git a/docs/html/preview/tv/design/images/apps-games-rows.jpg b/docs/html/preview/tv/design/images/apps-games-rows.jpg
new file mode 100644
index 0000000..5023655
--- /dev/null
+++ b/docs/html/preview/tv/design/images/apps-games-rows.jpg
Binary files differ
diff --git a/docs/html/preview/tv/design/images/atv-framed-med.png b/docs/html/preview/tv/design/images/atv-framed-med.png
new file mode 100644
index 0000000..e06f6e7
--- /dev/null
+++ b/docs/html/preview/tv/design/images/atv-framed-med.png
Binary files differ
diff --git a/docs/html/preview/tv/design/images/atv-home.jpg b/docs/html/preview/tv/design/images/atv-home.jpg
new file mode 100644
index 0000000..4b25bab
--- /dev/null
+++ b/docs/html/preview/tv/design/images/atv-home.jpg
Binary files differ
diff --git a/docs/html/preview/tv/design/images/focus.png b/docs/html/preview/tv/design/images/focus.png
new file mode 100644
index 0000000..df61f4d
--- /dev/null
+++ b/docs/html/preview/tv/design/images/focus.png
Binary files differ
diff --git a/docs/html/preview/tv/design/images/icon.png b/docs/html/preview/tv/design/images/icon.png
new file mode 100644
index 0000000..ae34e18
--- /dev/null
+++ b/docs/html/preview/tv/design/images/icon.png
Binary files differ
diff --git a/docs/html/preview/tv/design/images/overscan.png b/docs/html/preview/tv/design/images/overscan.png
new file mode 100644
index 0000000..fb7e4bc
--- /dev/null
+++ b/docs/html/preview/tv/design/images/overscan.png
Binary files differ
diff --git a/docs/html/preview/tv/design/images/recommendations.png b/docs/html/preview/tv/design/images/recommendations.png
new file mode 100644
index 0000000..942cd10
--- /dev/null
+++ b/docs/html/preview/tv/design/images/recommendations.png
Binary files differ
diff --git a/docs/html/preview/tv/design/images/search.jpg b/docs/html/preview/tv/design/images/search.jpg
new file mode 100644
index 0000000..c034939
--- /dev/null
+++ b/docs/html/preview/tv/design/images/search.jpg
Binary files differ
diff --git a/docs/html/preview/tv/design/images/settings.jpg b/docs/html/preview/tv/design/images/settings.jpg
new file mode 100644
index 0000000..1c5bf31
--- /dev/null
+++ b/docs/html/preview/tv/design/images/settings.jpg
Binary files differ
diff --git a/docs/html/preview/tv/design/index.jd b/docs/html/preview/tv/design/index.jd
new file mode 100644
index 0000000..b924a5c
--- /dev/null
+++ b/docs/html/preview/tv/design/index.jd
@@ -0,0 +1,65 @@
+page.title=Design for TV
+header.justLinks=1
+footer.hide=1
+@jd:body
+
+
+<p>The Android TV platform user interface provides the launch pad for your app's big screen
+ experience. It's important to understand how your app is presented in the main user interface and
+ how your app can help users get to the content they want quickly.</p>
+
+
+<h2>Home Screen</h2>
+
+<p>The Home Screen is the start of the user experience, providing search, content
+ recommendations, and access to apps and settings. This screen provides a rich and cinematic
+ overview of apps and content.</p>
+
+<img src="{@docRoot}preview/tv/design/images/atv-home.jpg" alt="TV Home screen" />
+
+
+<h2>Search</h2>
+
+<p>By bringing the power of Google search to the big screen, Android TV makes new, dynamic
+ connections between content. A favorite movie may lead to the discovery of a new music artist,
+ planning a trip to Paris might surface new YouTube content and photos.</p>
+
+<img src="{@docRoot}preview/tv/design/images/search.jpg" alt="Recommendations Row" />
+
+<p>To learn more about searching within your app, see
+ <a href="{@docRoot}preview/tv/ui/in-app-search.html">Searching in TV Apps</a>.
+
+<h2>Recommendations</h2>
+
+<p>The recommendations row on Android TV is a central feature of the Home Screen that allows
+ users quick access to dynamic and relevant content for their media-consumption activities. The
+ row is optimized for quick browsing of personalized content and activity resumption (on the
+ device and across devices), while also providing a way for users to act on meaningful new
+ content.</p>
+
+<img src="{@docRoot}preview/tv/design/images/recommendations.png" alt="Recommendations Row" />
+
+<p>
+ Recommendations are based on the user’s recent and frequent usage behaviors, as well as
+ expressed content preferences. They appear as cards that represent a system or app action,
+ notification, activity, or piece of actionable media. Your app can provide suggestions for the
+ recommendations row to help get your content noticed. To learn more, see
+ <a href="{@docRoot}preview/tv/ui/recommendations.html">Recommendations</a>.
+</p>
+
+
+<h2>Apps and Games</h2>
+
+<p>Apps and Games rows both have special areas on the Home Screen. Within their respective
+ areas, Apps and Games titles are ordered to reflect the user’s recent usage.</p>
+
+<img src="{@docRoot}preview/tv/design/images/apps-games-rows.jpg" alt="Apps and Games Rows" />
+
+
+<h2>Settings</h2>
+
+<p>Access to Settings is found at the bottom of the Home Screen. From here, the user can access
+ Android and device-specific settings.
+</p>
+
+<img src="{@docRoot}preview/tv/design/images/settings.jpg" alt="Settings Row" />
diff --git a/docs/html/preview/tv/design/patterns.jd b/docs/html/preview/tv/design/patterns.jd
new file mode 100644
index 0000000..cdba74c
--- /dev/null
+++ b/docs/html/preview/tv/design/patterns.jd
@@ -0,0 +1,86 @@
+page.title=Patterns for TV
+page.tags="design"
+@jd:body
+
+<p>As a developer of apps for TV, you should follow certain patterns to enable users to
+ quickly understand and operate your app. This section describes recommended design patterns
+ for TV apps.</p>
+
+<h2>Navigation, Focus and Selection</h2>
+
+<p>Users typically navigate TV devices using a directional pad (D-Pad). This type of controller
+ limits movement to up, down, left, and right. As you design your app for TV, make sure your
+ user interface has clear paths for two-axis navigation by aligning objects in lists and
+ grids.</p>
+
+<img src="{@docRoot}preview/tv/design/images/focus.png" alt="TV navigation and focus diagram" />
+
+<p>A key aspect of making your application work well with a D-Pad controller is to make sure
+ that there is always an object that is obviously in focus. Your app must clearly indicate
+ what object is focused, so users can easily see what action they can take. Use scale, shadow
+ brightness, opacity, animation or a combination of these attributes to help users see a focused
+ object.</p>
+
+
+<h2>Icons</h2>
+
+<p>Apps on TV devices require some additional icon images for presentation in the system
+ user interface, including home screen launcher images (banners) and recommendation icons.
+ The visual specifications for these icons are shown below.</p>
+
+
+<h3>Banners</h3>
+
+<p>App Banners represent your app on the home screen of TV devices and serve and as a way for
+ users to launch your app. Here are specific requirements for a banner image:
+</p>
+
+<ul>
+ <li>Size: 320 x 180 px, xhdpi resource</li>
+ <li>Text should be included in the image. If your app is available in more than one
+ language, you must provide versions of the banner image for each supported language.</li>
+</ul>
+
+
+<h3>Recommendation Icons</h3>
+
+<p>Recommendation cards include a small icon that is imposed over a colored background.
+ An example and specifications for the this icon are shown below:</p>
+
+<img src="{@docRoot}preview/tv/design/images/icon.png" alt="Recommendation icon examples" />
+
+<p>Here are the requirements for recommendation icons:</p>
+
+<ul>
+ <li>Monocolor: size 16x16dp, white (#fff) icon with transparent background, PNG format</li>
+ <li>Graphics should be centered within the icon image</li>
+</ul>
+
+<p class="note">
+ <strong>Note:</strong> Your app icon image may be desaturated and blended for some card
+ displays.
+</p>
+
+
+<h2>Background Images</h2>
+
+<p>Background images are displayed in the background of your app to provide additional visual
+ interest, information, or branding. The BrowseFragment and DetailsFragment classes in the Leanback
+ support library provide specific support for background images and for updating them as items gain
+ and lose focus. Here are the specific requirements for background images:</p>
+
+<ul>
+ <li>Full color, 1920 x 1080 pixels</li>
+</ul>
+
+<p class="note">
+ <strong>Note:</strong> If the image does not meet this requirement, it is scaled to fit.
+</p>
+
+<h2>Audio Feedback</h2>
+
+<p>Sounds on Android TV bring a cinematic quality to the interaction experience. You should
+ consider adding sounds for user actions or to provide feedback when a user is only partially
+ visually engaged with the screen (e.g., because they are distracted or multitasking).
+ You should also consider using sounds as alternatives to visual messages, for example to indicate
+ that a user has reached the end of a list or is trying to navigate to an undefined location.</p>
diff --git a/docs/html/design/tv/principles.jd b/docs/html/preview/tv/design/principles.jd
similarity index 66%
rename from docs/html/design/tv/principles.jd
rename to docs/html/preview/tv/design/principles.jd
index 5c0ce10..106fa96 100644
--- a/docs/html/design/tv/principles.jd
+++ b/docs/html/preview/tv/design/principles.jd
@@ -1,45 +1,33 @@
-page.title=Design Principles for TV
+page.title=Creative Vision for TV
@jd:body
-<p>Users bring a specific set of expectations to the experience of watching TV, versus
- interacting with a phone or tablet. These principles have been developed by the Android User
+<p>Users bring a specific set of expectations when watching TV, versus
+ interacting with a phone or tablet. These guidelines have been developed by the Android User
Experience Team to guide creation of the Android TV platform and the apps that run on it.</p>
<h2>Casual Consumption</h2>
<p>The TV is an entertainment interface, not a computer or mobile device. Optimize for
activities that put content at the center: from the casual posture of movie-watching, to
- edge-of-seat, immersive gameplay, to hanging out with friends in a living room.</p>
+ immersive gameplay, to hanging out with friends in a living room.</p>
-<p>Users expect immediate access to to content when they turn on a TV. Get users into the action
+<p>Users expect immediate access to content when they turn on a TV. Get users into the action
fast, be it the big game, their favorite show, or a game with friends. The next piece of content
to watch or play should only be a click or two away.</p>
-<p>
- [add a visual]
-</p>
-
<h2>Cinematic Experience</h2>
<p>Create immersive experiences for the user. Design for as little user interface and as much
- content as possible on each screen. Use visual imagery, movement and sound to inform and delight
+ content as possible on each screen. Use visual imagery, movement, and sound to inform and delight
users. Avoid using on-screen text to convey information and purpose. Tell your story with pictures
and sound.</p>
-<p>
- [add a visual]
-</p>
-
<h2>Simplicity</h2>
-<p>An Android TV should be simple and magical. It’s all about finding and enjoying content and
+<p>Android TV is simple and magical. It’s all about finding and enjoying content and
apps with the least amount of friction. Minimize the number of navigation steps required to
perform actions. Build apps with the fewest screens possible between app entry and content
immersion. Avoid making users enter text whenever possible, and use voice interfaces when you
require text input.</p>
-
-<p>
- [add a visual]
-</p>
diff --git a/docs/html/design/tv/style.jd b/docs/html/preview/tv/design/style.jd
similarity index 62%
rename from docs/html/design/tv/style.jd
rename to docs/html/preview/tv/design/style.jd
index 479ed91..67a7096 100644
--- a/docs/html/design/tv/style.jd
+++ b/docs/html/preview/tv/design/style.jd
@@ -9,13 +9,14 @@
<h2>Layouts</h2>
<p>The difference between a TV experience that feels right and one that does not greatly depends
- on the number, spacing and size of on-screen elements. Although TV sizes and resolutions have
+ on the number, spacing, and size of on-screen elements. Although TV sizes and resolutions have
steadily increased over time, users expect TV experiences to be relatively simple and
uncluttered.</p>
<p>The additional resolution and screen area afforded by modern displays is best used to display
things at better quality, rather than greater quantity. For example, use your layouts to show
- large, beautiful pieces of content, or resize type for both easy reading and generous spacing.</p>
+ large, beautiful pieces of content, or to resize type for both easy reading and generous spacing.
+</p>
<p>If you are creating an app for browsing and playing content, use the prebuilt fragments in the
Leanback support library. These layouts have been built specifically for use on TV devices with
@@ -23,33 +24,28 @@
see the <a href="{@docRoot}preview/tv/build-ui/index.html">User Interfaces</a> guide.
</p>
-<p>Here are some additional recommendations for creating functional and attractive layout for TV
+<p>Here are some additional recommendations for creating functional and attractive layouts for TV
apps:</p>
<ul>
- <li><p>Build layouts designed for landscape orientation. TV screens always use in this
- orientation.</p></li>
- <li><p>Put on-screen navigational controls on the left or right side of the screen and
- save the vertical space for content.</p></li>
- <li><p>Create UIs that are divided into sections, by using Fragments and use view groups
- like GridView instead of ListView to make better use of the horizontal screen space.</p></li>
- <li><p>Add sufficient margins between layout controls to avoid a cluttered interface.</p></li>
+ <li>Design layouts for landscape orientation. TV screens always use this
+ orientation.</li>
+ <li>Design your artwork assets for best viewing at HD resolution (1920 x 1080 pixels).</li>
+ <li>Put on-screen navigational controls on the left or right side of the screen, and
+ save the vertical space for content.</li>
+ <li>Use Fragments to create UIs that are divided into sections, and use view groups
+ like GridView instead of ListView to make better use of the horizontal screen space.</li>
+ <li>Avoid a cluttered interface by adding sufficient margins between layout controls.</li>
</ul>
-<h3>Screen Size</h3>
-
-<p>TV devices running Android are intended to operate at HD resolution (1920 x 1080 pixels) or
- higher. Design your artwork assets for best viewing at this resolution.</p>
-
-
<h3>Overscan</h3>
<p>During the evolution of TV technology, overscan originally described an area of TV content
outside of a safe zone that most TVs could reliably display. Even on some of today’s HDTV flat
screens, areas outside that zone may not be visible.</p>
-<img src="{@docRoot}design/tv/images/overscan.png" alt="image alt text" />
+<img src="{@docRoot}preview/tv/design/images/overscan.png" alt="Overscan borders for TV" />
<p>Build a 10% margin into your TV screen designs to account for overscan area the TV may not
display correctly. On a 1920 x 1080 pixel screen, this margin should be a minimum of 27px from the
@@ -63,8 +59,9 @@
not match what you see on a computer screen.</p>
<p>Subtle hue or brightness differences between elements may disappear or be over-emphasized on
- TV screens. Some color gradient combinations will show bands. You should avoid pure whites and
- highly saturated colors in large areas of the screen (especially reds, greens and blues). You
+ TV screens. Some color gradient combinations will show bands. You should avoid pure whites on
+ large areas of the screen. For highly saturated colors (especially reds, greens and blues) you
+ should review them when used to fill significant areas of the screen. You
should also avoid using very dark or muddy colors, as TV settings may display these colors with
exaggerated contrast, causing them to be indistinguishable.</p>
@@ -76,17 +73,14 @@
be 18sp. We recommend the following guidelines for TV apps:</p>
<ul>
- <li>Browse Titles: Regular 44sp</li>
- <li>Browse Menu Category Text: 20sp at 50% transparency</li>
- <li>Browse Focused Menu Category Text: 24sp with no transparency</li>
- <li>Row Category Titles focused: 20sp with no transparency</li>
- <li>Row Category Titles focused: 20sp at 50% transparency</li>
- <li>Details Content Titles: 34sp</li>
- <li>Details Subtext: 14sp</li>
+ <li><strong>Card Titles:</strong> Roboto Condensed 16sp</li>
+ <li><strong>Card Subtext:</strong> Roboto Condensed 12sp</li>
+ <li><strong>Browse Screen Title:</strong> Roboto Regular 44sp</li>
+ <li><strong>Browse Category Title:</strong> Roboto Condensed 20sp</li>
+ <li><strong>Details Content Titles:</strong> Roboto Regular 34sp</li>
+ <li><strong>Details Subtext:</strong> Roboto Regular 14sp</li>
</ul>
-<p>[visual showing text on a TV screen (sidebar position)]</p>
-
<p>Some TVs have strong sharpness and contrast settings as their defaults. These picture
settings make thin and light typefaces look jagged and make the text difficult for people to read.
Therefore you should avoid thin or light typefaces on TV.</p>
@@ -94,15 +88,14 @@
<h2>Text</h2>
<p>Use text in TV apps sparingly. The position of users relative to a TV screen
- (typically about 10 away) makes it harder for users to read text and the expectation of users in a
- TV environment not conducive to reading. Follow these tips for the best handling of text in your
- app:</p>
+ (typically about 10 feet away) makes it harder for users to read text. Users also don't expect to
+ read much in a TV environment. Follow these tips for the best handling of text in your app:</p>
<ul>
<li>Break text into small chunks that users can quickly scan.</li>
<li>Use light text on a dark background. This style is easier to read on a TV.</li>
<li>Avoid lightweight fonts or fonts that have both very narrow and very broad
- strokes. Use simple sans-serif fonts and use anti-aliasing to increase readability.</li>
- <li>Use layout-relative sizing rather than absolute sizing and density-independent
+ strokes. Use simple sans-serif fonts and anti-aliasing to increase readability.</li>
+ <li>Use layout-relative sizing rather than absolute sizing, and density-independent
pixel units instead of absolute pixel units.</li>
-</ul>
\ No newline at end of file
+</ul>
diff --git a/docs/html/preview/tv/games/index.jd b/docs/html/preview/tv/games/index.jd
new file mode 100644
index 0000000..763eada
--- /dev/null
+++ b/docs/html/preview/tv/games/index.jd
@@ -0,0 +1,152 @@
+ikpage.title=Games on TV
+page.tags="controller"
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#display">Display</li></a></li>
+ <li><a href="#control">Input Devices</li></a></li>
+ <li><a href="#manifest">Manifest</li></a></li>
+ <li><a href="#gpgs">Google Play Game Services</li></a></li>
+ <li><a href="#web">Web</a></li>
+ </ol>
+</div>
+</div>
+
+<p>The television screen presents a number of considerations that may be new to mobile-game
+developers. These areas include its large size, its control scheme, and the fact that all
+players are viewing it simultaneously.</p>
+
+
+<h2 id=display>Display</h2>
+<p>The two main things to keep in mind when developing games for the TV screen are its nature as a
+shared display and the need to design your game for a landscape orientation.</p>
+<h3>Shared display</h3>
+<p>A living-room TV poses design challenges for multiplayer games, in that all players can see
+everything. This issue is especially relevant to games (such as card games or strategy games) that
+rely on each player’s possession of hidden information.</p>
+<p>Some mechanisms you can implement to address the problem of one player’s eavesdropping
+on another’s information are:</p>
+<ul>
+<li>A blinder on the screen to help conceal information. For example, in a
+turn-based game like a word or card game, one player at a time might view the display. When the
+player finishes a move, the game allows him or her to cover the screen with a blinder that
+blocks anyone from viewing secret information. When the next player begins a turn, the blinder
+opens to reveal his or her own information.</li>
+<li>A companion app, running on a phone or tablet, can enable a player to conceal
+information.</li>
+</ul>
+<h3>Landscape display</h3>
+<p>A TV is always sideways: You can’t turn it, and there is no
+portrait orientation. Always design your TV games to be displayed in landscape
+mode.</p>
+
+<h2 id="control">Input Devices</h2>
+<p>TVs don't have touch interfaces, so it's even more important to get your controls right and make
+ sure that players find them intuitive and fun to use. The separation of controller from device also
+introduces some other issues to pay attention to, like keeping track of multiple players'
+controllers, and handling disconnects gracefully.</p>
+<h3>D-pad</h3>
+<p>Plan your control scheme around a directional pad (D-pad) control, since this control set is the
+default for Android TV devices. The player needs to be able to use a D-Pad in all aspects of the
+game–not just controlling core
+gameplay, but also navigating menus and ads. For this reason, you should also ensure that your
+Android TV game does not refer to a touch interface: for example, an Android TV game cannot tell a
+player to <strong>Tap to skip</strong>.</p>
+<p>How you shape the player's interaction with the controller can be key to achieving a great user
+experience:
+ <ul>
+ <p><li><strong>Communicate Controller Requirements up Front</strong> - Use your Play Store description to communicate to the player any expectations about
+controllers. If a game is better suited to a gamepad with a joystick than one with only a D-pad,
+make this fact clear. A player who uses an ill-suited controller for a game is likely to have a
+subpar experience–and penalize your game in the ratings.</p>
+ <p><li><strong>Use Consistent Button Mapping</strong> - Intuitive and flexible button mapping is key to a good user experience. For example,
+you can adhere to accepted custom by using the A button to <code>Accept</code>, and the B button to
+<code>Cancel</code>. You can also offer flexibility in the form of remappability. For more
+information on button mapping, see <a
+href="http://developer.android.com/training/game-controllers/controller-input.html">Handling
+Controller Actions</a>.</p>
+ <p><li><strong>Detect Controller Capabilities and Adjust Accordingly</strong> - Query the controller about its capabilities in order to optimize the match between
+controller and game. For example, you may intend for a player to steer an object by waving the
+controller in the air. If a player's controller lacks accelerometer and gyroscope hardware, however,
+waving will not work. When, however, your game queries the controller and discovers that motion detection
+is not supported, it can switch over to an alternative, available control scheme.
+For more information on querying controller capabilities, see <a
+href="http://developer.android.com/training/game-controllers/compatibility.html">Supporting
+Controllers Across Android Versions</a>.</p>
+ </ul>
+<h3>Back-button behavior</h3>
+<p>The Back button should never act as a toggle. For example, do not use it to both open and close
+a menu. It should only navigate backward, breadcrumb-style, through the previous screens the player has
+been on. For example: Game play > Game pause screen > Game
+main screen > Android home screen.</p>
+<p>Since the Back button should only perform linear (backward) navigation, you may use the
+back button to leave an in-game menu (opened by a different button) and return to gameplay. For
+more information about design for navigation, see <a
+href="http://developer.android.com/design/patterns/navigation.html">Navigation with Back and
+Up</a>. To learn about implementation, refer to <a
+href="http://developer.android.com/training/implementing-navigation/temporal.html">Providing Proper
+Back Navigation</a>. </p>
+<h3>Handling multiple controllers</h3>
+<p>When multiple players are playing a game, each with his or her own controller, it is important
+to map each player-controller pair. For information on how to implement controller-number
+identification, see <a href="http://developer.android.com/reference/android/view/InputDevice.html
+#getControllerNumber(">Input Devices</a>) on the Android developer site.</p>
+<h3>Handling disconnects</h3>
+<p>When a controller is disconnected in the middle of gameplay, the game should pause, and a dialog
+should appear prompting the disconnected player to reconnect his or her controller.</p>
+<p>The dialog should also offer troubleshooting tips (for example, a pop-up dialog telling the player to
+"Check your Bluetooth connection"). For more information on implementing input-device support, see <a
+href="http://developer.android.com/training/game-controllers/controller-input.html">Supporting Game
+Controllers"</a>. Specific information about Bluetooth connections is at <a
+href="http://developer.android.com/guide/topics/connectivity/bluetooth.html">Bluetooth</a>.</p>
+
+<h2 id="manifest">Manifest</h2>
+<p>Games are displayed in a separate row from regular apps in the launcher. Android TV uses the
+<code>android:isGame</code> flag to differentiate games from non-game apps. You can assign it a
+value of either <code>true</code> or <code>false</code>. For example:</p>
+<pre class="fragment"><application>
+ ...
+< android:isGame=["true" | "false"] >
+ ...
+</application></pre>
+
+<h2 id="gpgs">Google Play Game Services</h2>
+<p>If your game integrates Google Play Game Services, you should keep in mind a number of
+considerations pertaining to achievements, sign-on, saving games, and multiplayer play.</p>
+<h3>Achievements</h3>
+<p>Your game should include at least five (earnable) achievements. Only a user controlling gameplay
+from a supported input device should be able to earn achievements. For more information on
+Achievements and how to implement them, see <a
+href="https://developers.google.com/games/services/android/achievements">Achievements in
+Android</a>.</p>
+<h3>Sign-in</h3>
+<p>Your game should attempt to sign the user in on launch. If the player declines sign-in several
+times in a row, your game should stop asking. Learn more about sign-in at <a
+href="https://developers.google.com/games/services/training/signin">Implementing Sign-in on
+Android</a>.</p>
+<h3>Saving</h3>
+<p>We highly recommend using Play Services <a
+href="https://developers.google.com/games/services/common/concepts/cloudsave">Cloud Save</a> to
+store your game save. Your game should bind game saves to a specific Google account, so as to be
+uniquely identifiable even across devices: Whether the player is using a handset or a TV, the game
+should be able to pull the same game-save information from his or her account.</p>
+<p>You should also provide an option in your game's UI to allow the player to delete locally and
+cloud-stored data. You might put the option in the game's <code>Settings</code> screen. For
+specifics on implementing Cloud Save, see <a
+href="https://developers.google.com/games/services/android/cloudsave">Cloud Save in Android</a>.</p>
+<h3>Multiplayer experience</h3>
+<p>A game offering a multiplayer experience must allow at least two players to enter a room. For
+further information on multiplayer games in Android, see the <a
+href="https://developers.google.com/games/services/android/realtimeMultiplayer">Real-time
+Multiplayer</a> and <a href="">Turn-based Multiplayer</a> documentation on the Android developer
+site.</p>
+
+<h2 id="web">Web</h2>
+<p>We discourage including web browsing in games for Android TV. The television set is not well-suited for browsing,, either in terms of display or control scheme.</p>
+<p class="note"><strong>Note:</strong> You can use the {@link android.webkit.WebView} class for logins to services like Google+ and
+Facebook. </p>
+
diff --git a/docs/html/preview/tv/images/android-tv-remote.png b/docs/html/preview/tv/images/android-tv-remote.png
new file mode 100644
index 0000000..d15fbc5
--- /dev/null
+++ b/docs/html/preview/tv/images/android-tv-remote.png
Binary files differ
diff --git a/docs/html/preview/tv/images/home-recommendations.png b/docs/html/preview/tv/images/home-recommendations.png
new file mode 100644
index 0000000..ef97145
--- /dev/null
+++ b/docs/html/preview/tv/images/home-recommendations.png
Binary files differ
diff --git a/docs/html/preview/tv/index.jd b/docs/html/preview/tv/index.jd
new file mode 100644
index 0000000..dd35908
--- /dev/null
+++ b/docs/html/preview/tv/index.jd
@@ -0,0 +1,22 @@
+page.title=Android TV Apps
+
+@jd:body
+
+<p>Android offers a rich user experience that's optimized for apps running on large screen
+ devices, such as high-definition televisions. Apps on TV offer new opportunities to
+ delight your users from the comfort of their couch.</p>
+
+<p>This guide helps you build apps for TV devices, including:</p>
+
+<ul>
+ <li>How to set up your development environment</li>
+ <li>How to build user interfaces for TV</li>
+ <li>Guidelines for building games for TV</li>
+</ul>
+
+<p>Prepare your app for its big screen debut!</p>
+
+<p>
+ <strong><a href="{@docRoot}preview/tv/start/index.html">Get Started ></a></strong>
+</p>
+
diff --git a/docs/html/preview/tv/start/hardware-features.jd b/docs/html/preview/tv/start/hardware-features.jd
new file mode 100644
index 0000000..ddec496
--- /dev/null
+++ b/docs/html/preview/tv/start/hardware-features.jd
@@ -0,0 +1,183 @@
+page.title=Hardware Features on TV
+page.tags="unsupported"
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#unsupported-features">Unsupported Hardware Features</a></li>
+ <li><a href="#workaround-features">Handling Unsupported Features</a></li>
+ <li><a href="#check-features">Checking Available Features</a>
+ <ol>
+ <li><a href="#no-touchscreen">Touch screen</a></li>
+ <li><a href="#no-camera">Camera</a></li>
+ <li><a href="#no-gps">GPS</a></li>
+ </ol>
+
+ </li>
+ </ol>
+</div>
+</div>
+
+<p>TVs do not have some of the hardware features found on other Android devices.
+Touch screens, cameras, and GPS receivers are some of the most commonly used hardware features
+which are typically not available on a TV. When you build an app for TV, you must carefully
+consider if your app can handle not having these features and, if necessary, work around them.</p>
+
+<p>This guide discusses the hardware features not available on TV devices and shows you how to
+work around those limitations in your app. For more information on filtering and declaring
+features in the manifest, see the
+<a href="{@docRoot}guide/topics/manifest/uses-feature-element.html">uses-feature</a> guide.</p>
+
+
+<h2 id="unsupported-features">Unsupported Hardware Features</h2>
+
+<p>TVs have a different purpose from other devices, and so they do not have hardware
+features that other Android-powered devices often have. For this reason, the Android system
+does not support the following features for a TV device:
+
+<table>
+<tr>
+<th>Hardware</th>
+<th>Android feature descriptor</th>
+</tr>
+<tr>
+<td>Camera</td>
+<td>android.hardware.camera</td>
+</tr>
+<tr>
+<td>GPS</td>
+<td>android.hardware.location.gps</td>
+</tr>
+<tr>
+<td>Microphone</td>
+<td>android.hardware.microphone</td>
+</tr>
+<tr>
+<td>Near Field Communications (NFC)</td>
+<td>android.hardware.nfc</td>
+</tr>
+<tr>
+<td>Telephony</td>
+<td>android.hardware.telephony</td>
+</tr>
+<tr>
+<td>Touchscreen</td>
+<td>android.hardware.touchscreen</td>
+</tr>
+</table>
+</p>
+
+
+<h2 id="check-features">Checking Available Features</h2>
+
+<p>To check if a feature is available at runtime, call {@link
+ android.content.pm.PackageManager#hasSystemFeature(String)}. This method takes a single string
+ argument that specifies the feature you want to check. For example, to check for a touch screen,
+ use {@link android.content.pm.PackageManager#hasSystemFeature(String)} with the argument
+ {@link android.content.pm.PackageManager#FEATURE_TOUCHSCREEN}.</p>
+
+<p>The following code example demonstrates how to detect the availability of a hardware features
+ at runtime:</p>
+
+<pre>
+// Check if the telephony hardware feature is available.
+if (getPackageManager().hasSystemFeature("android.hardware.telephony")) {
+ Log.d("Mobile Test", "Running on phone");
+// Check if android.hardware.touchscreen feature is available.
+} else if (getPackageManager().hasSystemFeature("android.hardware.touchscreen")) {
+ Log.d("Tablet Test", "Running on devices that don't support telephony but "+
+ "do have a touch screen.");
+} else {
+ Log.d("TV Test", "Running on a TV!");
+}
+</pre>
+
+<p class="note">
+ <strong>Note:</strong> You can also use the {@link android.app.UiModeManager#getCurrentModeType
+ UiModeManager.getCurrentModeType()} method to detect the current platform type. For TV devices,
+ this method returns a value of {@link android.content.res.Configuration#UI_MODE_TYPE_TELEVISION
+ Configuration.UI_MODE_TYPE_TELEVISION}.
+</p>
+
+
+<h2 id="workaround-features">Handling Unsupported Features</h2>
+
+<p>Depending on the design and functionality of your app, you may be able to work around certain
+ hardware features being unavailable. This section discusses how to work around specific hardware
+ features.</p>
+
+
+<h3 id="no-touchscreen">Touch screen</h3>
+
+<p>Android doesn't support touch screen interaction for TV devices, since most TVs don't have touch
+ screens, and using a touch screen is not consistent with a viewing environment where the user is
+ seated 10 feet away from the display.</p>
+
+<p>On TV devices, you should work around this limitation by supporting navigation using a directional
+ pad (D-pad) on TV remote control. For more information on properly supporting navigation using
+ TV-friendly controls, see <a href="{@docRoot}preview/tv/ui/navigation.html">Navigation for
+ TV</a>.</p>
+
+<p>You can explicitly declare if your application requires (or does not require) a touch screen
+ by including the following entry in your manifest:</p>
+
+<pre>
+<uses-feature android:name="android.hardware.touchscreen"
+ android:required="false"/>
+</pre>
+
+
+<h3 id="no-camera">Camera</h3>
+
+<p>Although a TV typically does not have a camera, you can still provide a photography-related
+ application on a TV. For example, if you have an app that takes, views and edits photos, you can
+ disable its picture-taking functionality for TVs and still allow users to view and even edit
+ photos. If you decide that you want to enable your camera-related application to work on a
+ TV device without a camera, you can add an attribute to your app manifest declaring that
+ a camera is not required by your app:</p>
+
+<pre>
+<uses-feature android:name="android.hardware.camera" android:required="false" />
+</pre>
+
+<p>If you enable your application to run without a camera, you should add code to your application
+that detects if the camera feature is available and makes adjustments to the operation of your app.
+The following code example demonstrates how to detect the presence of a camera:</p>
+
+<pre>
+// Check if the camera hardware feature is available.
+if (getPackageManager().hasSystemFeature("android.hardware.camera")) {
+ Log.d("Camera test", "Camera available!");
+} else {
+ Log.d("Camera test", "No camera available. View and edit features only.");
+}
+</pre>
+
+
+<h3 id="no-gps">GPS</h3>
+
+<p>TVs are stationary, indoor devices, and do not have built-in global positioning system (GPS)
+ receivers. If your application uses location information, you can still allow users to search
+ for a location, or use a static location provider such as a zip code configured during the
+ TV device setup.</p>
+
+<pre>
+LocationManager locationManager = (LocationManager) this.getSystemService(
+ Context.LOCATION_SERVICE);
+Location location = locationManager.getLastKnownLocation("static");
+Geocoder geocoder = new Geocoder(this);
+Address address = null;
+
+try {
+ address = geocoder.getFromLocation(location.getLatitude(),
+ location.getLongitude(), 1).get(0);
+ Log.d("Zip code", address.getPostalCode());
+
+} catch (IOException e) {
+ Log.e(TAG, "Geocoder error", e);
+}
+</pre>
+
diff --git a/docs/html/preview/tv/start/index.jd b/docs/html/preview/tv/start/index.jd
new file mode 100644
index 0000000..b75fee0
--- /dev/null
+++ b/docs/html/preview/tv/start/index.jd
@@ -0,0 +1,231 @@
+page.title=Get Started with TV Apps
+page.tags="leanback","recyclerview","launcher"
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#prerequisites">Prerequisites</a></li>
+ <li><a href="#dev-project">Setup a TV Project</a>
+ <ul>
+ <li><a href="#tv-activity">Create a TV Activity</a></li>
+ <li><a href="#tv-libraries">Add TV Support Libraries</a></li>
+ </ul>
+ </li>
+ <li><a href="#build-it">Build TV Apps</a></li>
+ <li><a href="#run">Run TV Apps</a></li>
+
+ </ol>
+</div>
+</div>
+
+<p>This guide describes how to prepare your development environment and projects for building
+ TV apps, including updating your existing app to run on TV devices.</p>
+
+
+<h2 id="prerequisites">Prerequisites</h2>
+
+<p>Before you begin setting up to build apps for TV, you must:</p>
+
+<ul>
+ <li><strong><a href="{@docRoot}preview/setup-sdk.html">
+ Set up the Preview SDK</a></strong>
+ <br>
+ The preview SDK provides the developer tools needed to build and test apps for TV.
+ </li>
+ <li><strong><a href="{@docRoot}preview/setup-sdk.html#project">
+ Create a Preview SDK Project</a></strong>
+ <br>
+ In order to access new APIs for TV devices, you must create a project that targets the preview
+ release level or modify an existing project to target the preview release.
+ </li>
+</ul>
+
+
+<h2 id="dev-project">Set up a TV Project</h2>
+
+<p>TV apps use the same structure as those for phones and tablets. This means you can modify
+ your existing apps to also run on TV devices or create new apps based on what you already know
+ about building apps for Android. This section discusses how to modify an existing app, or create a
+ new one, to run on TV devices.</p>
+
+<p>These are the main steps to creating an app that runs on TV devices. Only the first
+ is required:</p>
+
+<ul>
+ <li><strong>Activity for TV</strong> - (Required) In your application manifest, you must
+ declare an activity that is intended to run on TV devices.</li>
+ <li><strong>TV Support Libraries</strong> - (Optional) There are several Support Libraries
+ available for TV devices that provide widgets for building user interfaces.</li>
+</ul>
+
+
+<h3 id="tv-activity">Create a TV Activity</h3>
+
+<p>An application intended to run on TV devices must declare a launcher activity for TV
+ in its manifest using a {@code android.intent.category.LEANBACK_LAUNCHER} intent filter.
+ This filter identifies your app as being built for TV, enabling it to be displayed in the
+ Google Play store app running on TV devices. Declaring this intent also identifies which activity
+ in your app should be launched when a user selects its icon on the TV home screen.</p>
+
+<p class="caution">
+ <strong>Caution:</strong> If you do not include the {@code LEANBACK_LAUNCHER} intent filter in
+ your app, it is not visible to users running the Google Play store on TV devices. Also, if your
+ app does not have this filter when you load it onto a TV device using developer tools, the app
+ does not appear in the TV user interface.
+</p>
+
+<p>The following code snippet shows how to include this intent filter in your manifest:</p>
+
+<pre>
+<application>
+ ...
+ <activity
+ android:name="com.example.android.MainActivity"
+ android:label="@string/app_name" >
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name="com.example.android.<strong>TvActivity</strong>"
+ android:label="@string/app_name"
+ android:theme="@android:style/Theme.Leanback">
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="<strong>android.intent.category.LEANBACK_LAUNCHER</strong>" />
+ </intent-filter>
+
+ </activity>
+</application>
+</pre>
+
+<p>The second activity manifest entry in the example above specifies that activity as
+ the main one when your app launches on an TV device.</p>
+
+<p>If you have an existing app that you are modifying for TV use, your app should not use the same
+ activity layout for TV that it does for phones and tablets. The user interface of your TV app (or
+ TV portion of your existing app) should provide a simpler interface that can be easily navigated
+ using a remote control from a couch. For guidelines on designing an app for TV, see the
+ <a href="{@docRoot}design/tv/index.html">TV Design</a> guide. For more instructions on
+ developing a user interface appropriate to TV, see the
+ <a href="{@docRoot}preview/tv/ui/index.html">TV User Interface</a> guide.
+</p>
+
+
+<h3 id="tv-libraries">Add TV Support Libraries</h3>
+
+<p>The Preview SDK includes support libraries that are intended for use with TV apps. These
+ libraries provide APIs and user interface widgets for use on TV devices. The libraries are
+ located in the {@code <sdk>/extras/android/support/} directory where you installed the
+ Preview SDK. Here is a list of the libraries and their general purpose:</p>
+
+<ul>
+ <li><strong>v17 leanback library</strong> - Provides user interface widgets for TV, including
+ {@code BrowseFragment}, {@code DetailsFragment}, and {@code SearchFragment}.
+ <ul>
+ <li>SDK location: {@code <sdk>/extras/android/support/v17/leanback}</li>
+ <li>Gradle dependency: {@code com.android.support:leanback-v17:20.0.+}</li>
+ <li>Contains resources: Yes</li>
+ </ul>
+ </li>
+ <li><strong>v7 recyclerview library</strong> - Provides classes for managing display of long
+ lists in a memory efficient manner. Several classes in the v17 leanback library depend on the
+ classes in this library.
+ <ul>
+ <li>SDK location: {@code <sdk>/extras/android/support/v7/recyclerview}</li>
+ <li>Gradle dependency: {@code com.android.support:recyclerview-v7:20.0.+}</li>
+ <li>Contains resources: No</li>
+ </ul>
+ </li>
+</ul>
+
+<p class="note">
+ <strong>Note:</strong> You are not required to use these support libraries for your TV app.
+ However, we strongly recommend using them, particularly for apps that provide a media catalog
+ browsing interface.
+</p>
+
+<p>If you decide to use the v17 leanback library for your app, you should note that it is
+ dependent on the
+ <a href="{@docRoot}tools/support-library/features.html#v4">v4 support library</a>. This means
+ that apps that use the leanback support library should include all of these support
+ libraries:</p>
+
+<ul>
+ <li>v17 leanback support library</li>
+ <li>v7 recyclerview support library</li>
+ <li>v4 support library</li>
+</ul>
+
+<p>The v17 leanback library contain resources, which requires
+ you to take specific steps to include it in app projects. For instructions on
+ importing a support library with resources, see
+ <a href="http://developer.android.com/tools/support-library/setup.html#libs-with-res">
+ Support Library Setup</a>.
+</p>
+
+
+<h2 id="build-it">Build TV Apps</h2>
+
+<p>After you have completed the steps described above, it's time to start building apps for
+ the big screen! Check out these additional topics to help you build your app for TV:
+
+<ul>
+ <li><a href="{@docRoot}preview/tv/ui/index.html">User Interface</a> - The user interface of
+ TV devices is different from those of other Android devices. See this topic to find out how
+ to build TV user interfaces and to learn about the widgets provided to simplify that task.
+ </li>
+ <li><a href="{@docRoot}preview/tv/games/index.html">Games for TV</a> - TV devices are great
+ platforms for games. See this topic for information on building great game experiences for
+ TV.</li>
+ <li><a href="{@docRoot}preview/tv/start/hardware-features.html">Hardware features</a> - TV
+ devices do not contain hardware features normally found on other Android devices. See this
+ topic for information on unsupported hardware features and what to do about them.
+ </li>
+</ul>
+
+
+<h2 id="run">Run TV Apps</h2>
+
+<p>Running your app is an important part of the development process. The AVD Manager in the
+ Android SDK provides the device definitions that allows you to create virtual TV devices for
+ running and testing your applications.</p>
+
+<p>To create an virtual TV device:</p>
+
+<ol>
+ <li>Start the AVD Manager. For more information, see the
+ <a href="{@docRoot}tools/help/avd-manager.html">AVD Manager</a> help.</li>
+ <li>In the AVD Manager dialog, click the <strong>Device Definitions</strong> tab.</li>
+ <li>Select one of the Android TV device definitions, such as
+ <strong>Large Android TV</strong>, and click <strong>Create AVD</strong>.</li>
+ <li>Select the emulator options and click <strong>OK</strong> to create the AVD.
+ <p class="note">
+ <strong>Note:</strong> For best performance of the TV emulator device, enable the <strong>Use
+ Host GPU</strong> option and CPU platform image that supports hardware acceleration. For
+ more information on hardware acceleration of the emulator, see
+ <a href="{@docRoot}tools/devices/emulator.html#acceleration">Using the Emulator</a>.
+ </p>
+ </li>
+</ol>
+
+<p>To test your application on the virtual TV device:</p>
+
+<ol>
+ <li>Compile your TV application in your development environment.</li>
+ <li>Run the application from your development environment and choose the TV virtual device as
+ the target.</li>
+</ol>
+
+<p>For more information about using emulators see, <a href="{@docRoot}tools/devices/emulator.html">
+Using the Emulator</a>. For more information about deploying apps to emulators from
+Eclipse with ADT, see <a href="{@docRoot}http://developer.android.com/tools/building/building-eclipse.html">
+Building and Running from Eclipse with ADT</a>.</p>
+
diff --git a/docs/html/preview/tv/ui/browse.jd b/docs/html/preview/tv/ui/browse.jd
new file mode 100644
index 0000000..d6b97c1
--- /dev/null
+++ b/docs/html/preview/tv/ui/browse.jd
@@ -0,0 +1,198 @@
+page.title=BrowseFragment
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#layout">Media Browse Layout</a></li>
+ <li><a href="#lists">Displaying Media Lists</a></li>
+ <li><a href="#background">Updating the Background</a></li>
+ </ol>
+
+</div>
+</div>
+
+<p>The <a href="{@docRoot}preview/tv/start/index.html#tv-libraries">Leanback support library</a>
+ provides several APIs for displaying and browsing media catalogs
+ on the TV devices. This guide discusses how to use the classes provided by this library to
+ implement a user interface for browsing music or videos from your app's media catalog.</p>
+
+
+<h2 id="layout">Media Browse Layout</h2>
+
+<p>The {@code BrowseFragment} class in the Leanback support library allows you to create a primary
+ layout for browsing categories and rows of media items with a minimum of code. The following
+ example shows how to create a layout that contains a {@code BrowseFragment}:</p>
+
+<pre>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ >
+
+ <fragment
+ <strong>android:name="android.support.v17.leanback.app.BrowseFragment"</strong>
+ android:id="@+id/browse_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+</LinearLayout>
+</pre>
+
+<p>In order to work with this layout in an activity, retrieve the {@code BrowseFragment} element
+ from the layout. Use the methods in {@code BrowseFragment} to set display parameters such as the
+ icon, title and whether category headers are enabled. The following code sample demonstrates how
+ to set the layout parameters for a {@code BrowseFragment} in a layout:</p>
+
+<pre>
+public class BrowseMediaActivity extends Activity {
+
+ public static final String TAG ="BrowseActivity";
+
+ protected BrowseFragment mBrowseFragment;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.browse_fragment);
+
+ final FragmentManager fragmentManager = getFragmentManager();
+ <strong>mBrowseFragment = (BrowseFragment) fragmentManager.findFragmentById(
+ R.id.browse_fragment);</strong>
+
+ // Set display parameters for the BrowseFragment
+ mBrowseFragment.setHeadersState(BrowseFragment.HEADERS_ENABLED);
+ mBrowseFragment.setTitle(getString(R.string.app_name));
+ mBrowseFragment.setBadgeDrawable(getResources().getDrawable(R.drawable.ic_launcher));
+ mBrowseFragment.setBrowseParams(params);
+
+ }
+}
+</pre>
+
+
+<h2 id="lists">Displaying Media Lists</h2>
+
+<p>The {@code BrowseFragment} allows you to define and display browsable media content categories and
+ media items from a media catalog using adapters and presenters. Adapters enable you to connect to
+ local or online data sources that contain your media catalog information. Presenter classes hold
+ data about media items and provide layout information for displaying an item on screen.</p>
+
+<p>The following example code shows an implementation of a presenter for displaying string
+ data:</p>
+
+<pre>
+public class StringPresenter extends Presenter {
+ private static final String TAG = "StringPresenter";
+
+ public ViewHolder onCreateViewHolder(ViewGroup parent) {
+ TextView textView = new TextView(parent.getContext());
+ textView.setFocusable(true);
+ textView.setFocusableInTouchMode(true);
+ textView.setBackground(
+ parent.getContext().getResources().getDrawable(R.drawable.text_bg));
+ return new ViewHolder(textView);
+ }
+
+ public void onBindViewHolder(ViewHolder viewHolder, Object item) {
+ ((TextView) viewHolder.view).setText(item.toString());
+ }
+
+ public void onUnbindViewHolder(ViewHolder viewHolder) {
+ // no op
+ }
+}
+</pre>
+
+<p>Once you have constructed a presenter class for your media items, you can build and attach an
+ adapter to the {@code BrowseFragment} to display those items on screen for browsing by the user. The
+ following example code demonstrates how to construct an adapter to display categories and items
+ in those categories using the StringPresenter class shown in the previous code example:</p>
+
+<pre>
+private ArrayObjectAdapter mRowsAdapter;
+private static final int NUM_ROWS = 4;
+
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+ ...
+
+ buildRowsAdapter();
+}
+
+private void buildRowsAdapter() {
+ mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+
+ for (int i = 0; i < NUM_ROWS; ++i) {
+ ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(
+ new StringPresenter());
+ listRowAdapter.add("Media Item 1");
+ listRowAdapter.add("Media Item 2");
+ listRowAdapter.add("Media Item 3");
+ HeaderItem header = new HeaderItem(i, "Category " + i, null);
+ mRowsAdapter.add(new ListRow(header, listRowAdapter));
+ }
+
+ mBrowseFragment.setAdapter(mRowsAdapter);
+}
+</pre>
+
+<p>This example shows a static implementation of the adapters. A typical media browsing
+ application uses data from an online database or web service. For an example of a browsing
+ application that uses data retrieved from the web, see the
+ <a href="http://github.com/googlesamples/androidtv-leanback">Android TV</a> sample app.</p>
+
+
+<h2 id="background">Updating the Background</h2>
+
+<p>In order to add visual interest to a media-browsing app on TV, you can update the background
+ image as users browse through content. This technique can make interaction with your app feel more
+ cinematic and enjoyable for users.</p>
+
+<p>The Leanback support library provides a {@code BackgroundManager} class for changing the
+ background of your TV app activity. The following example shows how to create a simple method
+ for updating the background within your TV app activity:</p>
+
+<pre>
+protected void updateBackground(Drawable drawable) {
+ BackgroundManager.getInstance(this).setDrawable(drawable);
+}
+</pre>
+
+<p>Many of the existing media-browse apps automatically update the background as the user
+ navigates through media listings. In order to do this, you can set up a selection listener to
+ automatically update the background based on the user's current selection. The following example
+ shows you how to set up an {@code OnItemSelectedListener}
+ class to catch selection events and update the background:</p>
+
+<pre>
+protected void clearBackground() {
+ BackgroundManager.getInstance(this).setDrawable(mDefaultBackground);
+}
+
+protected OnItemSelectedListener getDefaultItemSelectedListener() {
+ return new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(Object item, Row row) {
+ if (item instanceof Movie ) {
+ URI uri = ((Movie)item).getBackdropURI();
+ updateBackground(uri);
+ } else {
+ clearBackground();
+ }
+ }
+ };
+}
+</pre>
+
+<p class="note">
+ <strong>Note:</strong> The implementation above is a simple example shown for purposes of
+ illustration. When creating this function in your own app, you should consider running the
+ background update action in a separate thread for better performance. In addition, if you are
+ planning on updating the background in response to users scrolling through items, consider adding
+ a time to delay a background image update until the user settles on an item. This technique avoids
+ excessive background image updates.
+</p>
diff --git a/docs/html/preview/tv/ui/details.jd b/docs/html/preview/tv/ui/details.jd
new file mode 100644
index 0000000..8b8fa8b5
--- /dev/null
+++ b/docs/html/preview/tv/ui/details.jd
@@ -0,0 +1,214 @@
+page.title=DetailFragment
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#details-presenter">Build a Details Presenter</a></li>
+ <li><a href="#details-fragment">Extend the Details Fragment</a>
+ <li><a href="#activity">Creating a Details Activity</a></li>
+ <li><a href="#item-listener">Listener for Clicked Items</a></li>
+ </li>
+ </ol>
+</div>
+</div>
+
+<p>The media browsing interface classes provided by the
+ <a href="{@docRoot}preview/tv/start/index.html#tv-libraries">Leanback support library</a>
+ include classes for displaying additional information about a media item, such as a description
+ or reviews, and for taking action on that item, such as purchasing it or playing its content. This
+ section discusses how to create a presenter class for media item details and extend the
+ {@code DetailsFragment} class to implement a details view for a media item when it
+ is selected by a user.
+</p>
+
+<p class="note">
+ <strong>Note:</strong> The implementation example shown here uses an additional activity to
+ contain the {@code DetailsFragment}. However, it is possible to avoid creating a second activity
+ by replacing the current {@code BrowseFragment} with a {@code DetailsFragment} within the <em>same</em>
+ activity using fragment transactions. For more information on using fragment transactions, see the
+ <a href="{@docRoot}training/basics/fragments/fragment-ui.html#Replace">Building a Dynamic
+ UI with Fragments</a> training.
+</p>
+
+
+<h2 id="details-presenter">Build a Details Presenter</h2>
+
+<p>In the media browsing framework provided for by the leanback support library, you use
+ presenter objects to control the display of data on screen, including media item details. The
+ framework provides the {@code AbstractDetailsDescriptionPresenter} class for this purpose, which
+ is a nearly complete implementation of the presenter for media item details. All you have to do is
+ implement the {@code onBindDescription()} method to bind the view fields to your data objects, as shown in
+ the following code sample:</p>
+
+<pre>
+public class DetailsDescriptionPresenter
+ extends AbstractDetailsDescriptionPresenter {
+
+ @Override
+ protected void onBindDescription(ViewHolder viewHolder, Object itemData) {
+ MyMediaItemDetails details = (MyMediaItemDetails) itemData;
+ // In a production app, the itemData object contains the information
+ // needed to display details for the media item:
+ // viewHolder.getTitle().setText(details.getShortTitle());
+
+ // Here we provide static data for testing purposes:
+ viewHolder.getTitle().setText(itemData.toString());
+ viewHolder.getSubtitle().setText("2014 Drama TV-14");
+ viewHolder.getBody().setText("Lorem ipsum dolor sit amet, consectetur "
+ + "adipisicing elit, sed do eiusmod tempor incididunt ut labore "
+ + " et dolore magna aliqua. Ut enim ad minim veniam, quis "
+ + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
+ + "commodo consequat.");
+ }
+}
+</pre>
+
+
+<h2 id="details-fragment">Extend the Details Fragment</h2>
+
+<p>When you use the {@code DetailsFragment} class for displaying your media item details, you
+ extend that class to provide additional content such as a preview image and actions for the media
+ item. You can also provide additional content, such as a list of related media items.</p>
+
+<p>The following example code demonstrates how to use the presenter class you created in the
+ previous section, add a preview image and actions for the media item being viewed. This example
+ also shows the addition of a related media items row, which appears below the details listing.</p>
+
+<pre>
+public class MediaItemDetailsFragment extends DetailsFragment {
+ private static final String TAG = "MediaItemDetailsFragment";
+ private ArrayObjectAdapter mRowsAdapter;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ Log.i(TAG, "onCreate");
+ super.onCreate(savedInstanceState);
+
+ buildDetails();
+ }
+
+ private void buildDetails() {
+ ClassPresenterSelector selector = new ClassPresenterSelector();
+ // Attach your media item details presenter to the row presenter:
+ DetailsOverviewRowPresenter rowPresenter =
+ new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
+
+ selector.addClassPresenter(DetailsOverviewRow.class, rowPresenter);
+ selector.addClassPresenter(ListRow.class,
+ new ListRowPresenter());
+ mRowsAdapter = new ArrayObjectAdapter(selector);
+
+ Resources res = getActivity().getResources();
+ DetailsOverviewRow detailsOverview = new DetailsOverviewRow(
+ "Media Item Details");
+
+ // Add images and action buttons to the details view
+ detailsOverview.setImageDrawable(res.getDrawable(R.drawable.jelly_beans));
+ detailsOverview.addAction(new Action(1, "Buy $9.99"));
+ detailsOverview.addAction(new Action(2, "Rent $2.99"));
+ mRowsAdapter.add(detailsOverview);
+
+ // Add a Related items row
+ ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(
+ new StringPresenter());
+ listRowAdapter.add("Media Item 1");
+ listRowAdapter.add("Media Item 2");
+ listRowAdapter.add("Media Item 3");
+ HeaderItem header = new HeaderItem(0, "Related Items", null);
+ mRowsAdapter.add(new ListRow(header, listRowAdapter));
+
+ setAdapter(mRowsAdapter);
+ }
+}
+</pre>
+
+
+<h3 id="activity">Creating a Details Activity</h3>
+
+<p>Fragments such as the {@code DetailsFragment} must be contained within an activity in order
+ to be used for display. Creating an activity for your details view, separate from the browse
+ activity, enables you to invoke your details view using an Intent. This section explains how to
+ build an activity to contain your implementation of the detail view for your media items.</p>
+
+<p>Start creating the details activity by building a layout that references your implementation
+ of the {@code DetailsFragment}:</p>
+
+<pre>
+<!-- file: res/layout/details.xml -->
+
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+ <strong>android:name="com.example.android.mediabrowser.MediaItemDetailsFragment"</strong>
+ android:id="@+id/details_fragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+/>
+</pre>
+
+<p>Next, create an activity class that uses the layout shown in the previous code example:</p>
+
+<pre>
+public class DetailsActivity extends Activity
+{
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ <strong>setContentView(R.layout.details);</strong>
+ }
+}
+</pre>
+
+<p>Finally, add this new activity to the manifest. Remember to apply the Leanback theme to
+ ensure that the user interface is consistent with the media browse activity:</p>
+
+<pre>
+<application>
+ ...
+
+ <activity android:name=".DetailsActivity"
+ android:exported="true"
+ <strong>android:theme="@style/Theme.Leanback"/></strong>
+
+</application>
+</pre>
+
+
+<h3 id="item-listener">Listener for Clicked Items</h3>
+
+<p>After you have implemented the {@code DetailsFragment}, you must modify your main media
+ browsing view to move to your details view when a user clicks on a media item. In order to enable
+ this behavior, add an {@code OnItemClickedListener} object to the BrowseFragment that fires an
+ intent to start the item details activity.</p>
+
+<p>The following example shows how to implement a listener to start the details view when a user
+ clicks a media item in the main media browsing activity:</p>
+
+<pre>
+public class BrowseMediaActivity extends Activity {
+ ...
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ ...
+
+ // create the media item rows
+ buildRowsAdapter();
+
+ // add a listener for selected items
+ mBrowseFragment.setOnItemClickedListener(
+ new OnItemClickedListener() {
+ @Override
+ public void onItemClicked(Object item, Row row) {
+ System.out.println("Media Item clicked: " + item.toString());
+ Intent intent = new Intent(BrowseMediaActivity.this,
+ DetailsActivity.class);
+ // pass the item information
+ intent.getExtras().putLong("id", item.getId());
+ startActivity(intent);
+ }
+ });
+ }
+}
+</pre>
diff --git a/docs/html/preview/tv/ui/in-app-search.jd b/docs/html/preview/tv/ui/in-app-search.jd
new file mode 100644
index 0000000..3dbfcd2
--- /dev/null
+++ b/docs/html/preview/tv/ui/in-app-search.jd
@@ -0,0 +1,111 @@
+page.title=Adding Search to TV Apps
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#add-search-ui">Add Search User Interface</a></li>
+ </ol>
+
+</div>
+</div>
+
+
+<p>Users frequently have specific content in mind when using a media app. A search interface can
+ help your users get to the content they want faster than browsing. The Leanback library provides a
+ set of classes to enable a standard search interface within your app that is consistent with other
+ search functions on TV and provides features such as voice input.</p>
+
+<h2 id="add-search-ui">Add Search User Interface</h2>
+<p>When you use the BrowseFragment class for your media browsing interface, you can enable the
+ search icon by setting an OnClickListener to the BrowseFragment object. The following sample code
+ demonstrates this technique.</p>
+
+<pre>
+@Override
+public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.browse_activity);
+
+ mBrowseFragment = (BrowseFragment)
+ getFragmentManager().findFragmentById(R.id.browse_fragment);
+
+ ...
+
+ mBrowseFragment.setOnSearchClickedListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(BrowseActivity.this, SearchActivity.class);
+ startActivity(intent);
+ }
+ });
+
+ mBrowseFragment.setAdapter(buildAdapter());
+}
+</pre>
+
+<p class="note">
+ <strong>Note:</strong> You can set the color of the search icon using the
+ {@code setSearchAffordanceColor()} method of {@code BrowseFragment}.
+</p>
+
+<p>When a user selects the search icon, the system invokes a search activity via the defined
+ Intent. Your search activity should use a linear layout containing a SearchFragment. This fragment
+ must also implement the SearchFragment.SearchResultProvider interface in order to display the
+ results of a search. The following code sample shows how to extend the SearchFragment class to
+ provide a search interface and results:</p>
+
+<pre>
+public class MySearchFragment extends SearchFragment
+ implements SearchFragment.SearchResultProvider {
+
+ private static final int SEARCH_DELAY_MS = 300;
+ private ArrayObjectAdapter mRowsAdapter;
+ private Handler mHandler = new Handler();
+ private SearchRunnable mDelayedLoad;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+ setSearchResultProvider(this);
+ setOnItemClickedListener(getDefaultItemClickedListener());
+ mDelayedLoad = new SearchRunnable();
+ }
+
+ @Override
+ public ObjectAdapter getResultsAdapter() {
+ return mRowsAdapter;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newQuery) {
+ mRowsAdapter.clear();
+ if (!TextUtils.isEmpty(newQuery)) {
+ mDelayedLoad.setSearchQuery(newQuery);
+ mHandler.removeCallbacks(mDelayedLoad);
+ mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ mRowsAdapter.clear();
+ if (!TextUtils.isEmpty(query)) {
+ mDelayedLoad.setSearchQuery(query);
+ mHandler.removeCallbacks(mDelayedLoad);
+ mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
+ }
+ return true;
+ }
+}
+</pre>
+
+<p>This example code shown above is meant to be used with a separate {@code SearchRunnable}
+ class that runs the search query on a separate thread. This technique keeps potentially
+ slow-running queries from blocking the main user interface thread.</p>
+
diff --git a/docs/html/preview/tv/ui/index.jd b/docs/html/preview/tv/ui/index.jd
new file mode 100644
index 0000000..c861ec2
--- /dev/null
+++ b/docs/html/preview/tv/ui/index.jd
@@ -0,0 +1,40 @@
+page.title=User Interfaces for TV
+
+@jd:body
+
+
+<p>
+ Building an effective and engaging user interface for TV devices requires a firm understanding of what works well
+ in the context of a living room. Imagine a large screen that can be seen by many people at the
+ same time, controlled with a few buttons by users with limited attention, and you start to see the
+ challenges and opportunities of building an app for TV. Building apps for this environment
+ requires a different approach and different tools.</p>
+
+<p>This section discusses how to build a living room experience with your app, including
+ implementation instructions and creating user interface widgets built for TV. Also check out
+ <a href="{@docRoot}design/tv/index.html">Design for TV</a> for information and inspiration
+ on creating engaging user interfaces for TV devices.</p>
+
+<h2>Topics</h2>
+
+<dl>
+ <dt><b><a href="layouts.html">Layouts</a></b></dt>
+ <dd>Learn how to build app layouts for TV screens.</dd>
+
+ <dt><b><a href="navigation.html">Navigation</a></b></dt>
+ <dd>Learn how to build navigation for TV devices.</dd>
+
+ <dt><b><a href="browse.html">BrowseFragment</a></b></dt>
+ <dd>Learn how to use this fragment to build a browsing interface for media catalogs.</dd>
+
+ <dt><b><a href="details.html">DetailsFragment</a></b></dt>
+ <dd>Learn how to use this fragment to build a details page for media items.</dd>
+
+ <dt><b><a href="in-app-search.html">In-App Search</a></b></dt>
+ <dd>Learn how to use a built-for-TV user interface for searching within your app.</dd>
+
+ <dt><b><a href="recommendations.html">Recommendations</a></b></dt>
+ <dd>Learn how your app can contribute to the list of recommendations appearing on the home
+ screen and get your content noticed by users.</dd>
+</dl>
+
diff --git a/docs/html/preview/tv/ui/layouts.jd b/docs/html/preview/tv/ui/layouts.jd
new file mode 100644
index 0000000..0659826
--- /dev/null
+++ b/docs/html/preview/tv/ui/layouts.jd
@@ -0,0 +1,298 @@
+page.title=Layouts for TV
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#themes">Themes</a>
+ <ol>
+ <li><a href="#leanback-theme">Leanback Theme</a></li>
+ <li><a href="#notitle-theme">NoTitleBar Theme</a></li>
+ </ol>
+ </li>
+ <li><a href="#structure">Layout Structure</a>
+ <ol>
+ <li><a href="#overscan">Overscan</a></li>
+ </ol>
+ </li>
+ <li><a href="#visibility">Text and Controls Visibility</a></li>
+ <li><a href="#density-resources">Screen Density and Image Resources</a></li>
+ <li><a href="#anti-patterns">Layout Anti-Patterns</a></li>
+ <li><a href="#large-bitmaps">Handling Large Bitmaps</a></li>
+ </ol>
+
+</div>
+</div>
+
+<p>
+ A TV screen is typically viewed from about 10 feet away, and while it is much larger than most
+ other Android device displays, this type of screen does not provide the same level of precise
+ detail and color as a smaller device. These factors require that you create app layouts with
+ TV devices in mind in order to create a useful and enjoyable user experience.</p>
+
+<p>This guide provides direction and implementation details for building effective layouts inN
+ TV apps.</p>
+
+
+<h2 id="themes">Themes</h2>
+
+<p>Android <a href="{@docRoot}guide/topics/ui/themes.html">Themes</a> can provide a basis for
+ layouts in your TV apps. You should use a theme to modify the display of your app activities
+ that are meant to run on a TV device. This section explains which themes you should use.</p>
+
+
+<h3 id="leanback-theme">Leanback Theme</h3>
+
+<p>The Leanback library provides a standard theme for TV activities, called {@code
+ Leanback.Theme}, which establishes a consistent visual style for TV apps. Use of this theme is
+ recommended for most apps. This theme is recommended for any TV app that uses the Leanback
+ library classes. The following code sample shows how to apply this theme to a given
+ activity within an app:</p>
+
+<pre>
+<activity
+ android:name="com.example.android.TvActivity"
+ android:label="@string/app_name"
+ <strong>android:theme="@style/Theme.Leanback"</strong>>
+</pre>
+
+
+<h3 id="notitle-theme">NoTitleBar Theme</h3>
+
+<p>The title bar is a standard user interface element for Android apps on phones and tablets,
+ but it is not appropriate for TV apps. If you are not using the Leanback library classes,
+ you should apply this theme to your TV activities. The following code example from a TV app
+ manifest demonstrates how to apply this theme to remove the display of a title bar:
+</p>
+
+<pre>
+<application>
+ ...
+
+ <activity
+ android:name="com.example.android.TvActivity"
+ android:label="@string/app_name"
+ <strong>android:theme="@android:style/Theme.NoTitleBar"</strong>>
+ ...
+
+ </activity>
+</application>
+</pre>
+
+
+<h2 id="structure">Layout Structure</h2>
+
+<p>Layouts for TV devices should follow some basic guidelines to ensure they are usable and
+ effective on large screens. Follow these tips to build landscape layouts optimized for TV screens:
+</p>
+
+<ul>
+ <li>Build layouts with a landscape orientation. TV screens always display in landscape.</li>
+ <li>Put on-screen navigation controls on the left or right side of the screen and save the
+ vertical space for content.</li>
+ <li>Create UIs that are divided into sections, using <a
+ href="{@docRoot}guide/components/fragments.html"
+ >Fragments</a>, and use view groups like {@link android.widget.GridView} instead of {@link
+ android.widget.ListView} to make better use of the horizontal screen space.
+ </li>
+ <li>Use view groups such as {@link android.widget.RelativeLayout} or {@link
+ android.widget.LinearLayout} to arrange views. This approach allows the system to adjust the
+ position of the views to the size, alignment, aspect ratio, and pixel density of a TV screen.</li>
+ <li>Add sufficient margins between layout controls to avoid a cluttered UI.</li>
+</ul>
+
+
+<h3 id="overscan">Overscan</h3>
+
+<p>Layouts for TV have some unique requirements due to the evolution of TV standards and the
+ desire to always present a full screen picture to viewers. For this reason, TV devices may
+ clip the outside edge of an app layout in order to ensure that the entire display is filled.
+ This behavior is generally referred to as Overscan.</p>
+
+<p>In order to account for the impact of overscan and make sure that all the user interface
+ elements you place in a layout are actually shown on screen, you should incorporate a 10% margin
+ on all sides of your layout. This translates into a 27dp margin on the left and right edges and
+ a 48dp margin on the top and bottom of your base layouts for activities. The following
+ example layout demonstrates how to set these margins in the root layout for a TV app:
+</p>
+
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/base_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_marginTop="27dp"
+ android:layout_marginLeft="48dp"
+ android:layout_marginRight="48dp"
+ android:layout_marginBottom="27dp" >
+</LinearLayout>
+</pre>
+
+<p class="caution">
+ <strong>Caution:</strong> Do not apply overscan margins to your layout if you are using the
+ Leanback Support Library {@code BrowseFragment} or related widgets, as those layouts already
+ incorporate overscan-safe margins.
+</p>
+
+
+<h2 id="visibility">Text and Controls Visibility</h2>
+
+<p>
+The text and controls in a TV app layout should be easily visible and navigable from a distance.
+Follow these tips to make them easier to see from a distance :
+</p>
+
+<ul>
+ <li>Break text into small chunks that users can quickly scan.</li>
+ <li>Use light text on a dark background. This style is easier to read on a TV.</li>
+ <li>Avoid lightweight fonts or fonts that have both very narrow and very broad strokes.
+ Use simple sans-serif fonts and anti-aliasing to increase readability.</li>
+ <li>Use Android's standard font sizes:
+<pre>
+<TextView
+ android:id="@+id/atext"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"/>
+</pre>
+ </li>
+ <li>Ensure that all your view widgets are large enough to be clearly visible to someone
+ sitting 10 feet away from the screen (this distance is greater for very large screens). The
+ best way to do this is to use layout-relative sizing rather than absolute sizing, and
+ density-independent pixel units instead of absolute pixel units. For example, to set the
+ width of a widget, use wrap_content instead of a pixel measurement, and to set the margin
+ for a widget, use dip instead of px values.</li>
+</ul>
+
+
+<h2 id="density-resources">Screen Density and Image Resources</h2>
+
+<p>The common high-definition TV display resolutions are 720p, 1080i, and 1080p.
+ Your TV layout should target a screen size of 1920 x 1080 pixels, and then allow the Android
+ system to downscale your layout elements to 720p if necessary. In general, downscaling
+ (removing pixels) does not degrade your layout presentation quality. However, upscaling can
+ cause display artifacts that degrade the quality of your layout and have a negative impact on
+ the user experience of your app.</p>
+
+<p>
+ To get the best scaling results for images, provide them as
+ <a href="{@docRoot}tools/help/draw9patch.html">9-patch image</a> elements if possible. If you
+ provide low quality or small images in your layouts, they will appear pixelated, fuzzy, or
+ grainy. This is not a good experience for the user. Instead, use high-quality images.
+</p>
+
+<p>
+ For more information on optimizing layouts and resources for large screens see
+ <a href="{@docRoot}training/multiscreen/index.html">Designing for multiple screens</a>.
+</p>
+
+
+<h2 id="anti-patterns">Layout Anti-Patterns</h2>
+
+<p>There are a few approaches to building layouts for TV that you should avoid because they do not
+work well and lead to bad user experiences. Here are some user interface approaches you
+should specifically <em>not</em> use when developing a layout for TV.
+</p>
+
+<ul>
+ <li><strong>Re-using phone or tablet layouts</strong> - Do not reuse layouts from a phone or
+ tablet app without modification. Layouts built for other Android device form factors are not
+ well suited for TV devices and should be simplified for operation on a TV.</li>
+ <li><strong>ActionBar</strong> - While this user interface convention is recommended for use
+ on phones and tablets, it is not appropriate for a TV interface. In particular, using an
+ action bar options menu (or any pull-down menu for that matter) is strongly discouraged, due
+ to the difficulty in navigating such a menu with a remote control.</li>
+ <li><strong>ViewPager</strong> - Sliding between screens can work great on a phone or tablet,
+ but don't try this on a TV!</li>
+
+</ul>
+
+<p>For more information on designing layouts that are appropriate to TV, see the
+ <a href="{@docRoot}design/tv/index.html">TV Design</a> guide.</p>
+
+
+<h2 id="large-bitmaps">Handling Large Bitmaps</h2>
+
+<p>TV devices, like any other Android device, have a limited amount of memory. If you build your
+ app layout with very high-resolution images or use many high-resolution images in the operation
+ of your app, it can quickly run into memory limits and cause out of memory errors.
+ To avoid these types of problems, follow these tips:</p>
+
+<ul>
+ <li>Load images only when they're displayed on the screen. For example, when displaying multiple images in
+ a {@link android.widget.GridView} or
+ {@link android.widget.Gallery}, only load an image when
+ {@link android.widget.Adapter#getView(int, View, ViewGroup) getView()}
+ is called on the View's {@link android.widget.Adapter}.
+ </li>
+ <li>Call {@link android.graphics.Bitmap#recycle()} on
+ {@link android.graphics.Bitmap} views that are no longer needed.
+ </li>
+ <li>Use {@link java.lang.ref.WeakReference} for storing references
+ to {@link android.graphics.Bitmap} objects in an in-memory
+ {@link java.util.Collection}.</li>
+ <li>If you fetch images from the network, use {@link android.os.AsyncTask}
+ to fetch and store them on the device for faster access.
+ Never do network transactions on the application's UI thread.
+ </li>
+ <li>Scale down large images to a more appropriate size as you download them;
+ otherwise, downloading the image itself may cause an out of memory exception.
+ The following sample code demonstrates how to scale down images while downloading:
+<pre>
+ // Get the source image's dimensions
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ // This does not download the actual image, just downloads headers.
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(IMAGE_FILE_URL, options);
+ // The actual width of the image.
+ int srcWidth = options.outWidth;
+ // The actual height of the image.
+ int srcHeight = options.outHeight;
+
+ // Only scale if the source is bigger than the width of the destination view.
+ if(desiredWidth > srcWidth)
+ desiredWidth = srcWidth;
+
+ // Calculate the correct inSampleSize/scale value. This approach helps reduce
+ // memory use. This value should be a power of 2.
+ int inSampleSize = 1;
+ while(srcWidth / 2 > desiredWidth){
+ srcWidth /= 2;
+ srcHeight /= 2;
+ inSampleSize *= 2;
+ }
+
+ float desiredScale = (float) desiredWidth / srcWidth;
+
+ // Decode with inSampleSize
+ options.inJustDecodeBounds = false;
+ options.inDither = false;
+ options.inSampleSize = inSampleSize;
+ options.inScaled = false;
+ // Ensures the image stays as a 32-bit ARGB_8888 image.
+ // This preserves image quality.
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+
+ Bitmap sampledSrcBitmap = BitmapFactory.decodeFile(IMAGE_FILE_URL, options);
+
+ // Resize
+ Matrix matrix = new Matrix();
+ matrix.postScale(desiredScale, desiredScale);
+ Bitmap scaledBitmap = Bitmap.createBitmap(sampledSrcBitmap, 0, 0,
+ sampledSrcBitmap.getWidth(), sampledSrcBitmap.getHeight(), matrix, true);
+ sampledSrcBitmap = null;
+
+ // Save
+ FileOutputStream out = new FileOutputStream(LOCAL_PATH_TO_STORE_IMAGE);
+ scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
+ scaledBitmap = null;
+</pre>
+ </li>
+</ul>
+
diff --git a/docs/html/preview/tv/ui/navigation.jd b/docs/html/preview/tv/ui/navigation.jd
new file mode 100644
index 0000000..92b34cf
--- /dev/null
+++ b/docs/html/preview/tv/ui/navigation.jd
@@ -0,0 +1,136 @@
+page.title=Navigation for TV
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#d-pad-navigation">D-pad Navigation</a></li>
+ <li><a href="#focus-selection">Focus and Selection</a></li>
+ </ol>
+
+</div>
+</div>
+
+<p>TV devices provide a limited set of navigation controls for apps. Creating an effective
+ navigation scheme for your TV app depends on understanding these limited controls and the limits
+ of users' perception while operating your app. As you build your Android app for TVs,
+ you should pay special attention to how the user actually navigates around your app
+ when using remote control buttons instead of a touch screen.</p>
+
+<p>This guide shows you how to build an effective navigation scheme for your TV app.</p>
+
+
+<h2 id="d-pad-navigation">D-pad Navigation</h2>
+
+<p>On a TV device, users navigate with controls on a remote control device, using either a
+ directional pad (D-pad) or arrow keys. This type of control limits movement to up, down, left,
+ and right. To build a great TV-optimized app, you must provide a navigation scheme where
+ the user can quickly learn how to navigate your app using these limited controls.</p>
+
+<p>Follow these guidelines to build a navigation system that works well with a D-pad on a TV device:
+</p>
+
+<ul>
+ <li>Ensure that the D-pad can navigate to all the visible controls on the screen.</li>
+ <li>For scrolling lists with focus, D-pad up/down keys scroll the list, and the Enter key selects
+ an item in the list. Ensure that users can select an element in the list and that the list still
+ scrolls when an element is selected.</li>
+ <li>Ensure that movement between controls is straightforward and predictable.</li>
+</ul>
+
+<p>The Android framework handles directional navigation between layout elements automatically, so
+ you typically do not need to do anything extra for your app. However, you should thoroughly test
+ navigation with a D-pad control to discover any navigation problems. If you discover that your
+ screen layout makes navigation difficult, or if you want users to move through the layout in a
+ specific way, you can set up explicit directional navigation for your controls. The following
+ code sample shows how to define the next control to receive focus for a
+ {@link android.widget.TextView} layout object:</p>
+
+<pre>
+<TextView android:id="@+id/Category1"
+ android:nextFocusDown="@+id/Category2"\>
+</pre>
+
+<p>The following table lists all of the available navigation attributes for Android user interface
+widgets:</p>
+
+<table>
+ <tr>
+ <th>Attribute</th>
+ <th>Function</th>
+ </tr>
+ <tr>
+ <td>{@link android.R.attr#nextFocusDown}</td>
+ <td>Defines the next view to receive focus when the user navigates down.</td>
+ </tr>
+ <tr>
+ <td>{@link android.R.attr#nextFocusLeft}</td>
+ <td>Defines the next view to receive focus when the user navigates left.</td>
+ </tr>
+ <tr>
+ <td>{@link android.R.attr#nextFocusRight}</td>
+ <td>Defines the next view to receive focus when the user navigates right.</td>
+ </tr>
+ <tr>
+ <td>{@link android.R.attr#nextFocusUp}</td>
+ <td>Defines the next view to receive focus when the user navigates up.</td>
+ </tr>
+</table>
+
+<p>To use one of these explicit navigation attributes, set the value to the ID ({@code android:id}
+ value) of another widget in the layout. You should set up the navigation order as a loop, so that
+ the last control directs focus back to the first one.</p>
+
+<p class="note">
+ <strong>Note:</strong> You should only use these attributes to modify the navigation order if the
+ default order that the system applies does not work well.
+</p>
+
+
+<h2 id="focus-selection">Focus and Selection</h2>
+
+<p>The success of a navigation scheme on TV devices is strongly dependent on how easy it is for a
+ user to determine what user interface element is in focus on screen. If you do not provide clear
+ indications of what is in focus on screen (and therefore what item they can take action on),
+ users can quickly become frustrated and exit your app. By the same token, it is important
+ to always have an item in focus that a user can take action on immediately after your app starts,
+ and any time your app is not playing content.</p>
+
+<p>Your app layout and implementation should use color, size, animation, or a combination of
+ these attributes to help users easily determine what actions they can take next. Use a uniform
+ scheme for indicating focus across your application.</p>
+
+<p>Android provides <a href="{@docRoot}guide/topics/resources/drawable-resource.html#StateList">
+Drawable State List Resources</a> to implement highlights for selected and focused controls. The
+following code example demonstates how to indicate selection of a button object:
+</p>
+
+<pre>
+<!-- res/drawable/button.xml -->
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true"
+ android:drawable="@drawable/button_pressed" /> <!-- pressed -->
+ <item android:state_focused="true"
+ android:drawable="@drawable/button_focused" /> <!-- focused -->
+ <item android:state_hovered="true"
+ android:drawable="@drawable/button_focused" /> <!-- hovered -->
+ <item android:drawable="@drawable/button_normal" /> <!-- default -->
+</selector>
+</pre>
+
+<p>
+This layout XML applies the above state list drawable to a {@link android.widget.Button}:
+</p>
+<pre>
+<Button
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:background="@drawable/button" />
+</pre>
+
+<p>Make sure to provide sufficient padding within the focusable and selectable controls so that
+ the highlights around them are clearly visible.</p>
+
diff --git a/docs/html/preview/tv/ui/recommendations.jd b/docs/html/preview/tv/ui/recommendations.jd
new file mode 100644
index 0000000..2c78064
--- /dev/null
+++ b/docs/html/preview/tv/ui/recommendations.jd
@@ -0,0 +1,209 @@
+page.title=Making Recommendations
+
+@jd:body
+
+<div id="qv-wrapper">
+<div id="qv">
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#service">Create a Recommendations Service</a></li>
+ <li><a href="#build">Build Recommendations</a></li>
+ <li><a href="#run-service">Run Recommendations Service</a></li>
+ <li><a href="#DesignLandscapeLayouts">Design Landscape Layouts</a></li>
+ </ol>
+
+</div>
+</div>
+
+
+<p>Content recommendations appear as the first row of the TV launch screen after the first use
+ of the device. This row is intended to help users quickly find content they enjoy. Contributing
+ recommendations from your apps content catalog can help bring users back to your app.</p>
+
+
+<img src="{@docRoot}preview/tv/images/home-recommendations.png" alt="" id="figure1" />
+<p class="img-caption">
+ <strong>Figure 1.</strong> An example of the recommendations row.
+</p>
+
+
+<h2 id="service">Create a Recommendations Service</h2>
+
+<p>Content recommendations are created with background processing. In order for your application
+ to contribute to recommendations, you create a service that periodically adds listings from your
+ app's catalog to the system list of recommendations.</p>
+
+<p>The following code example illustrates how to extend the {@link android.app.IntentService} to
+ create a recommendation service for your application.</p>
+
+<pre>
+public class RecommendationsService extends IntentService {
+ private static final int MAX_RECOMMENDATIONS = 3;
+
+ public RecommendationsService() {
+ super("RecommendationService");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ MovieDatabase database = MovieDatabase.instance(getApplicationContext());
+ List<Movie> recommendations = database.recommendations();
+
+ int count = 0;
+
+ try {
+ for (Movie movie : recommendations) {
+ // build the individual content recommendations
+ buildRecommendation(getApplicationContext(), movie);
+
+ if (++count >= MAX_RECOMMENDATIONS) {
+ break;
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to update recommendation", e);
+ }
+ }
+}
+</pre>
+
+<p>In order for this class to be recognized and run as a service, you must register this service
+ using your app manifest. The following code snippet illustrates how to add this class as a
+ service:</p>
+
+<pre>
+<manifest ... >
+ <application ... >
+ ...
+
+ <service android:name=".RecommendationsService"
+ android:enabled="true" android:exported="true"/>
+ </application>
+</manifest>
+</pre>
+
+<h2 id="build">Build Recommendations</h2>
+
+<p>Once it starts running, your service must create recommendations and pass them to the Android
+ framework. The framework receives the recommendations as {@link android.app.Notification} objects
+ that use a specific style and are marked with a specific category.</p>
+
+<p>The following code example demonstrates how to get an instance of the {@link
+ android.app.NotificationManager}, build a recommendation, and post it to the manager:</p>
+
+<pre>
+public class RecommendationsService extends IntentService {
+
+ ...
+
+ public Notification buildRecommendation(Context context, Movie movie)
+ throws IOException {
+
+ if (mNotificationManager == null) {
+ mNotificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ Bundle extras = new Bundle();
+ if (mBackgroundUri != movie.getBackgroundUri()) {
+ extras.putString(EXTRA_BACKGROUND_IMAGE_URL, movie.getBackgroundUri());
+ }
+
+ // build the recommendation as a Notification object
+ Notification notification = new NotificationCompat.BigPictureStyle(
+ new NotificationCompat.Builder(context)
+ .setContentTitle(movie.getTitle())
+ .setContentText(movie.getDescription())
+ .setPriority(movie.getPriority())
+ .setOngoing(true)
+ .setCategory("recommendation")
+ .setLargeIcon(movie.getImage())
+ .setSmallIcon(movie.getSmallIcon())
+ .setContentIntent(buildPendingIntent(movie.getId()))
+ .setExtras(extras))
+ .build();
+
+ // post the recommendation to the NotificationManager
+ mNotificationManager.notify(movie.getId(), notification);
+ mNotificationManager = null;
+ return notification;
+ }
+
+ private PendingIntent buildPendingIntent(long id) {
+ Intent detailsIntent = new Intent(this, DetailsActivity.class);
+ detailsIntent.putExtra("id", id);
+
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
+ stackBuilder.addParentStack(DetailsActivity.class);
+ stackBuilder.addNextIntent(detailsIntent);
+ // Ensure each PendingIntent is unique
+ detailsIntent.setAction(Long.toString(id));
+
+ PendingIntent intent = stackBuilder.getPendingIntent(
+ 0, PendingIntent.FLAG_UPDATE_CURRENT);
+ return intent;
+ }
+}
+</pre>
+
+
+<h3 id="run-service">Run Recommendations Service</h3>
+
+<p>Your app's recommendation service must run periodically in order to create current
+ recommendations. In order to run your service, you should create a class that runs a timer and
+ invokes it at regular intervals. The following code example extends the {@link
+ android.content.BroadcastReceiver} class to start periodic execution of a recommendation service
+ every 12 hours:</p>
+
+<pre>
+public class BootupReceiver extends BroadcastReceiver {
+ private static final String TAG = "BootupActivity";
+
+ private static final long INITIAL_DELAY = 5000;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().endsWith(Intent.ACTION_BOOT_COMPLETED)) {
+ scheduleRecommendationUpdate(context);
+ }
+ }
+
+ private void scheduleRecommendationUpdate(Context context) {
+ AlarmManager alarmManager = (AlarmManager)context.getSystemService(
+ Context.ALARM_SERVICE);
+ Intent recommendationIntent = new Intent(context,
+ UpdateRecommendationsService.class);
+ PendingIntent alarmIntent = PendingIntent.getService(context, 0,
+ recommendationIntent, 0);
+
+ alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ INITIAL_DELAY,
+ AlarmManager.INTERVAL_HALF_DAY,
+ alarmIntent);
+ }
+}
+</pre>
+
+<p>In order for the {@link android.content.BroadcastReceiver} class to execute after a TV
+ device starts up, you must register this class in your app manifest and attach an intent filter
+ in order for the device boot process to complete. This sample code demonstrates how to add this
+ configuration to the manifest:</p>
+
+<pre>
+<manifest ... >
+ <application ... >
+ <receiver android:name=".BootupReceiver" android:enabled="true"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED"/>
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
+</pre>
+
+<p class="important">
+ <strong>Important:</strong> Receiving a boot completed notification requires that your app
+ request the {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission.
+ For more information, see {@link android.content.Intent#ACTION_BOOT_COMPLETED}.
+</p>
diff --git a/docs/html/sdk/index.jd b/docs/html/sdk/index.jd
index dc3b9dd..14a5e1d 100644
--- a/docs/html/sdk/index.jd
+++ b/docs/html/sdk/index.jd
@@ -4,45 +4,45 @@
header.hide=1
page.metaDescription=Download the official Android SDK to develop apps for Android-powered devices.
-sdk.version=22.6.2
+sdk.version=23.0.0
-sdk.linux32_bundle_download=adt-bundle-linux-x86-20140321.zip
-sdk.linux32_bundle_bytes=527971926
-sdk.linux32_bundle_checksum=943ae4d28fe7c79108c8bf2aafd5e6d2
+sdk.linux32_bundle_download=adt-bundle-linux-x86-20140624.zip
+sdk.linux32_bundle_bytes=378659422
+sdk.linux32_bundle_checksum=692e6135ed459f1e8a10498363f19f67
-sdk.linux64_bundle_download=adt-bundle-linux-x86_64-20140321.zip
-sdk.linux64_bundle_bytes=528187678
-sdk.linux64_bundle_checksum=f2a2153b5c7dbaeb86b550bf4f770c36
+sdk.linux64_bundle_download=adt-bundle-linux-x86_64-20140624.zip
+sdk.linux64_bundle_bytes=378966059
+sdk.linux64_bundle_checksum=0f14b4aed1eb1feed778ad6ed76ba01c
-sdk.mac64_bundle_download=adt-bundle-mac-x86_64-20140321.zip
-sdk.mac64_bundle_bytes=501955296
-sdk.mac64_bundle_checksum=4a08649cea9b098cdf7349f452294014
+sdk.mac64_bundle_download=adt-bundle-mac-x86_64-20140624.zip
+sdk.mac64_bundle_bytes=327367424
+sdk.mac64_bundle_checksum=7d16e83263259882901f12055a8fe3f2
-sdk.win32_bundle_download=adt-bundle-windows-x86-20140321.zip
-sdk.win32_bundle_bytes=535085536
-sdk.win32_bundle_checksum=b61495a6bf591cc374c31bce4fc46ec0
+sdk.win32_bundle_download=adt-bundle-windows-x86-20140624.zip
+sdk.win32_bundle_bytes=377325518
+sdk.win32_bundle_checksum=5655cd8be53c4b27c5242d81943c5a25
-sdk.win64_bundle_download=adt-bundle-windows-x86_64-20140321.zip
-sdk.win64_bundle_bytes=535287324
-sdk.win64_bundle_checksum=a6f4699bbdc5a29b371ed60610535651
+sdk.win64_bundle_download=adt-bundle-windows-x86_64-20140624.zip
+sdk.win64_bundle_bytes=377477237
+sdk.win64_bundle_checksum=0f1fa29a0f229e36ba0fb87bb7ee68d4
-sdk.linux_download=android-sdk_r22.6.2-linux.tgz
-sdk.linux_bytes=101050024
-sdk.linux_checksum=ff1541418a44d894bedc5cef10622220
+sdk.linux_download=android-sdk_r23-linux.tgz
+sdk.linux_bytes=137880976
+sdk.linux_checksum=fd768c56423e398b3e4aa8895c393bf5
-sdk.mac_download=android-sdk_r22.6.2-macosx.zip
-sdk.mac_bytes=74639394
-sdk.mac_checksum=2a319c862dd1dcf450bfe2a6b3d9c608
+sdk.mac_download=android-sdk_r23-macosx.zip
+sdk.mac_bytes=88015023
+sdk.mac_checksum=3869e5b9de8d69f90509568686fb7ce8
-sdk.win_download=android-sdk_r22.6.2-windows.zip
-sdk.win_bytes=108917505
-sdk.win_checksum=6e5bfdb7b9c1d231ed6ec78b31551cbf
+sdk.win_download=android-sdk_r23-windows.zip
+sdk.win_bytes=138459944
+sdk.win_checksum=9daba72b3a15a6154fe6ca1ada817553
-sdk.win_installer=installer_r22.6.2-windows.exe
-sdk.win_installer_bytes=87383126
-sdk.win_installer_checksum=2a68b8b22ecd0aba779b1581a914b395
+sdk.win_installer=installer_r23-windows.exe
+sdk.win_installer_bytes=90065639
+sdk.win_installer_checksum=4564d1f1b30c001c78a22eec40444e5f
diff --git a/docs/html/sdk/installing/create-project.jd b/docs/html/sdk/installing/create-project.jd
new file mode 100644
index 0000000..c0d523a
--- /dev/null
+++ b/docs/html/sdk/installing/create-project.jd
@@ -0,0 +1,86 @@
+page.title=Creating a Project
+
+@jd:body
+
+<p>Android Studio makes it easy to create Android apps for several form factors, such as phone,
+tablet, TV, Wear, and Google Glass. The <em>New Project</em> wizard lets you choose the form factors
+for your app and populates the project structure with everything you need to get started.</p>
+
+<p>Follow the steps in this section to create a project in Android Studio.</p>
+
+<h2>Step 1: Create a New Project</h2>
+
+<p>If you didn't have a project opened, Android Studio shows the Welcome screen.
+To create a new project, click <strong>New Project</strong>.</p>
+
+<p>If you had a project opened, Android Studio shows the development environment.
+To create a new project, click <strong>File</strong> > <strong>New Project</strong>.</p>
+
+<p>The next window lets you configure the name of your app, the package name, and the location
+of your project.</p>
+
+<img src="{@docRoot}images/tools/wizard2.png" alt="" width="500" height="381">
+<p class="img-caption"><strong>Figure 1.</strong> Choose a name for your project.</p>
+
+<p>Enter the values for your project and click <strong>Next</strong>.</p>
+
+
+<h2>Step 2: Select Form Factors and API Level</h2>
+
+<p>The next window lets you select the form factors supported by your app, such as phone, tablet,
+TV, Wear, and Google Glass. For each form factor, you can also select the API
+Level that your app requires. To get more information, click <strong>Help me choose</strong>.</p>
+
+<img src="{@docRoot}images/tools/wizard4.png" alt="" width="750" height="510">
+<p class="img-caption"><strong>Figure 2.</strong> Select the API Level.</p>
+
+<p>The API Level window shows the distribution of mobile devices running each version of Android,
+as shown in Figure 2. Click on an API level to see a list of features introduced in the corresponding
+version of Android. This helps you choose the minimum API Level that has all the features that
+your apps needs, so you can reach as many devices as possible. Then click <strong>OK</strong>.</p>
+
+<img src="{@docRoot}images/tools/wizard3.png" alt="" width="500" height="480">
+<p class="img-caption"><strong>Figure 3.</strong> Choose form factors for your app.</p>
+
+<p>Then, on the Form Factors Window, click <strong>Next</strong>.</p>
+
+
+<h2>Step 3: Add an Activity</h2>
+
+<p>The next screen lets you select an activity type to add to your app, as shown in Figure 4.
+This screen depends on the form factors you selected earlier.</p>
+
+<img src="{@docRoot}images/tools/wizard5.png" alt="" width="720" height="504">
+<p class="img-caption"><strong>Figure 4.</strong> Add an activity to your app.</p>
+
+<p>Choose an activity type and click <strong>Next</strong>.</p>
+
+
+<h2>Step 4: Configure Your Activity</h2>
+
+<p>The next screen lets you configure the activity to add to your app, as shown in Figure 6.</p>
+
+<img src="{@docRoot}images/tools/wizard6.png" alt="" width="450" height="385">
+<p class="img-caption"><strong>Figure 6.</strong> Choose a name for your activity.</p>
+
+<p>Enter the activity name, the layout name, and the activity title. Then click
+<strong>Finish</strong>.</p>
+
+
+<h2>Step 5: Develop Your App</h2>
+
+<p>Android Studio creates the default structure for your project and opens the development
+environment. If your app supports more than one form factor, Android Studio creates a module for
+each of them, as shown in Figure 7.</p>
+
+<img src="{@docRoot}images/tools/wizard7.png" alt="" width="750" height="509">
+<p class="img-caption"><strong>Figure 7.</strong> The default project structure for a mobile app.</p>
+
+<p>Now you are ready to develop your app. Fore more information, see the following links:</p>
+
+<ul>
+<li><a href="{@docRoot}training/">Training Lessons</a></li>
+<li><a href="{@docRoot}training/building-wearables.html">Building Apps for Wearables</a></li>
+<li><a href="{@docRoot}tv/">Android TV</a></li>
+<li><a href="https://developers.google.com/glass/">Google Glass</a></li>
+</ul>
diff --git a/docs/html/sdk/installing/installing-adt.jd b/docs/html/sdk/installing/installing-adt.jd
index 1e87cd8..b13983b 100644
--- a/docs/html/sdk/installing/installing-adt.jd
+++ b/docs/html/sdk/installing/installing-adt.jd
@@ -1,8 +1,8 @@
page.title=Installing the Eclipse Plugin
-adt.zip.version=22.6.3
-adt.zip.download=ADT-22.6.3.zip
-adt.zip.bytes=14590813
-adt.zip.checksum=3982259fd2cc81e53bbbe05dcd6529a7
+adt.zip.version=23.0.0
+adt.zip.download=ADT-23.0.0.zip
+adt.zip.bytes=104001830
+adt.zip.checksum=a1bc9b15b69b43bda358ea09a2feb5f7
@jd:body
@@ -84,7 +84,7 @@
<div class="sidebox-wrapper">
<div class="sidebox">
<h2>App Translations in Google Play</h2>
-<p>Google Play <a href="{@docRoot}distribute/tools/localization-checklist.html#gp-trans">App
+<p>Google Play <a href="{@docRoot}distribute/tools/localization-checklist.html#gp-trans">App
Translation Service</a> is available in the Developer Console to help you
localize your app for a global user base. You can browse qualified vendors, get
estimates, upload strings for translation, and then import the translations directly
diff --git a/docs/html/sdk/installing/studio.jd b/docs/html/sdk/installing/studio.jd
index 894514a..3b7a7da 100644
--- a/docs/html/sdk/installing/studio.jd
+++ b/docs/html/sdk/installing/studio.jd
@@ -1,5 +1,7 @@
page.title=Android Studio
page.tags="studio"
+page.image=images/resource-card-android-studio.png
+page.metaDescription=Learn about the new features in the beta release of our new IDE.
@jd:body
@@ -249,36 +251,36 @@
<td>Windows</td>
<td>
<a onclick="return onDownload(this)" id="win-studio"
- href="http://dl.google.com/android/studio/install/0.5.2/android-studio-bundle-135.1078000-windows.exe">
- android-studio-bundle-135.1078000-windows.exe
+ href="http://dl.google.com/android/studio/install/0.8.0/android-studio-bundle-135.1245622-windows.exe">
+ android-studio-bundle-135.1245622-windows.exe
</a>
</td>
- <td>519082997 bytes</td>
- <td>ac69889210c4d02ee3ccc1c0f3c5cf3c</td>
+ <td>380000036 bytes</td>
+ <td>5720baef7d492f2df7398a38dae2fa92</td>
</tr>
<tr>
<td><nobr>Mac OS X</nobr></td>
<td>
<a onclick="return onDownload(this)" id="mac-studio"
- href="http://dl.google.com/android/studio/install/0.5.2/android-studio-bundle-135.1078000-mac.dmg">
- android-studio-bundle-135.1078000-mac.dmg
+ href="http://dl.google.com/android/studio/install/0.8.0/android-studio-bundle-135.1245622-mac.dmg">
+ android-studio-bundle-135.1245622-mac.dmg
</a>
</td>
- <td>495989974 bytes</td>
- <td>8c7b1ef376b8ca206c99823d9e8fd54d</td>
+ <td>368451923 bytes</td>
+ <td>fa9da3625db44687576c5c4e8f96280e</td>
</tr>
<tr>
<td>Linux</td>
<td>
<a onclick="return onDownload(this)" id="linux-studio"
- href="http://dl.google.com/android/studio/install/0.5.2/android-studio-bundle-135.1078000-linux.tgz">
- android-studio-bundle-135.1078000-linux.tgz
+ href="http://dl.google.com/android/studio/install/0.8.0/android-studio-bundle-135.1245622-linux.tgz">
+ android-studio-bundle-135.1245622-linux.tgz
</a>
</td>
- <td>520523870 bytes</td>
- <td>689238d5e632fd236b13f9c6d49f0cb4</td>
+ <td>417756987 bytes</td>
+ <td>c70dd2e4035484b84f0ad0046a34f136</td>
</tr>
</table>
@@ -396,6 +398,18 @@
<div class="toggle-content opened">
<p><a href="#" onclick="return toggleContent(this)">
<img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img"
+ alt=""/>Android Studio v0.8.0</a> <em>(June 2014)</em>
+ </p>
+
+ <div class="toggle-content-toggleme">
+ <p>Added support for Android Wear projects.</p>
+ <p>See <a href="http://tools.android.com/recent">tools.android.com</a> for a full list of changes.</p>
+ </div>
+</div>
+
+<div class="toggle-content closed">
+ <p><a href="#" onclick="return toggleContent(this)">
+ <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img"
alt=""/>Android Studio v0.5.2</a> <em>(May 2014)</em>
</p>
@@ -570,7 +584,7 @@
if (os) {
/* set up primary ACE download button */
$('#download-ide-button').show();
- $('#download-ide-button').append("Download Android Studio Beta <span class='small'>v0.5.2</span>"
+ $('#download-ide-button').append("Download Android Studio Beta <span class='small'>v0.8.0</span>"
+ "<br/> <span class='small'>with the Android SDK for " + os + "</span>");
$('#download-ide-button').click(function() {return onDownload(this,true);}).attr('href', bundlename);
diff --git a/docs/html/tools/revisions/build-tools.jd b/docs/html/tools/revisions/build-tools.jd
index d8abab0..0abec2e 100644
--- a/docs/html/tools/revisions/build-tools.jd
+++ b/docs/html/tools/revisions/build-tools.jd
@@ -77,6 +77,25 @@
<div class="toggle-content opened">
<p><a href="#" onclick="return toggleContent(this)">
<img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img"
+ alt=""/>Build Tools, Revision 20.0.0</a> <em>(June 2014)</em>
+ </p>
+ <div class="toggle-content-toggleme">
+
+ <dl>
+ <dt>General Notes:</dt>
+ <dd>
+ <ul>
+ <li>Added support for Android Wear.</li>
+ </ul>
+ </dd>
+ </dl>
+
+ </div>
+</div>
+
+<div class="toggle-content closed">
+ <p><a href="#" onclick="return toggleContent(this)">
+ <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img"
alt=""/>Build Tools, Revision 19.1.0</a> <em>(May 2014)</em>
</p>
<div class="toggle-content-toggleme">
diff --git a/docs/html/tools/revisions/platforms.jd b/docs/html/tools/revisions/platforms.jd
index 5d1d13b..b5495c2 100644
--- a/docs/html/tools/revisions/platforms.jd
+++ b/docs/html/tools/revisions/platforms.jd
@@ -47,6 +47,34 @@
components will not be available for download.</p>
+<h2 id="4.4">Android 4.4W</h2>
+
+<div class="toggle-content opened">
+ <p><a href="#" onclick="return toggleContent(this)">
+ <img src="{@docRoot}assets/images/triangle-opened.png"
+class="toggle-content-img" alt="" />Revision 1</a> <em>(June 2014)</em>
+ </p>
+
+ <div class="toggle-content-toggleme">
+
+ <p>Initial release for Android Wear.</p>
+ <p>Dependencies:</p>
+ <ul>
+ <li>Android SDK Platform-tools r20 or higher is required.</li>
+ <li>Android SDK Tools 23.0 or higher is required.</li>
+ </ul>
+ </div>
+
+ <h3>Device Definitions</h3>
+
+ <p>The platform includes the following device definitions for use in creating Android Virtual
+ Devices in the <a href="{@docRoot}tools/help/avd-manager.html">AVD Manager</a>:</p>
+
+ <ul>
+ <li>Android Wear Round (320 x 320, hdpi)</li>
+ <li>Android Wear Square (320 x 320, hdpi)</li>
+ </ul>
+</div>
<h2 id="4.4">Android 4.4</h2>
diff --git a/docs/html/tools/sdk/eclipse-adt.jd b/docs/html/tools/sdk/eclipse-adt.jd
index 124e58d..8826ce3e 100644
--- a/docs/html/tools/sdk/eclipse-adt.jd
+++ b/docs/html/tools/sdk/eclipse-adt.jd
@@ -56,6 +56,38 @@
<div class="toggle-content opened">
<p><a href="#" onclick="return toggleContent(this)">
<img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img"
+ alt=""/>ADT 23.0.0</a> <em>(June 2014)</em>
+ </p>
+
+ <div class="toggle-content-toggleme">
+<dl>
+ <dt>Dependencies:</dt>
+
+ <dd>
+ <ul>
+ <li>Java 7 or higher is required if you are targeting the L Developer Preview.</li>
+ <li>Java 1.6 or higher is required if you are targeting other releases.</li>
+ <li>Eclipse Indigo (Version 3.7.2) or higher is required.</li>
+ <li>This version of ADT is designed for use with
+ <a href="{@docRoot}tools/sdk/tools-notes.html">SDK Tools r23.0.0</a>.
+ If you haven't already installed SDK Tools r23.0.0 into your SDK, use the
+ Android SDK Manager to do so.</li>
+ </ul>
+ </dd>
+
+ <dt>General Notes:</dt>
+ <dd>
+ <ul>
+ <li>Added the Android Wear tools and system images.</li>
+ </ul>
+ </dd>
+</dl>
+</div>
+</div>
+
+<div class="toggle-content closed">
+ <p><a href="#" onclick="return toggleContent(this)">
+ <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img"
alt=""/>ADT 22.6.3</a> <em>(April 2014)</em>
</p>
diff --git a/docs/html/tools/sdk/tools-notes.jd b/docs/html/tools/sdk/tools-notes.jd
index f490053..6c74a71 100644
--- a/docs/html/tools/sdk/tools-notes.jd
+++ b/docs/html/tools/sdk/tools-notes.jd
@@ -28,6 +28,37 @@
<div class="toggle-content opened">
<p><a href="#" onclick="return toggleContent(this)">
<img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img"
+ alt=""/>SDK Tools, Revision 23.0.0</a> <em>(June 2014)</em>
+ </p>
+
+ <div class="toggle-content-toggleme">
+
+ <dl>
+ <dt>Dependencies:</dt>
+
+ <dd>
+ <ul>
+ <li>Android SDK Platform-tools revision 19 or later.</li>
+ <li>If you are developing in Eclipse with ADT, note that this version of SDK Tools is
+ designed for use with ADT 23.0.0 and later. If you haven't already, update your
+ <a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT Plugin</a> to 23.0.0.</li>
+ <li>If you are developing outside Eclipse, you must have
+ <a href="http://ant.apache.org/">Apache Ant</a> 1.8 or later.</li>
+ </ul>
+ </dd>
+
+ <dt>General Notes:</dt>
+ <dd>
+ <ul>
+ <li>Added the Android Wear tools and system images.</li>
+ </ul>
+ </dd>
+ </div>
+</div>
+
+<div class="toggle-content closed">
+ <p><a href="#" onclick="return toggleContent(this)">
+ <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-content-img"
alt=""/>SDK Tools, Revision 22.6.4</a> <em>(June 2014)</em>
</p>
diff --git a/docs/html/tools/tools_toc.cs b/docs/html/tools/tools_toc.cs
index fb4659f..93e5976 100644
--- a/docs/html/tools/tools_toc.cs
+++ b/docs/html/tools/tools_toc.cs
@@ -11,7 +11,7 @@
<span class="en">Adding SDK Packages</span></a></li>
</ul>
</li>
-
+
<li class="nav-section">
<div class="nav-section-header">
@@ -20,6 +20,8 @@
<ul>
<li><a href="<?cs var:toroot ?>sdk/installing/migrate.html">
Migrating from Eclipse</a></li>
+ <li><a href="<?cs var:toroot ?>sdk/installing/create-project.html">
+ Creating a Project</a></li>
<li><a href="<?cs var:toroot ?>sdk/installing/studio-tips.html">
Tips and Tricks</a></li>
<li><a href="<?cs var:toroot ?>sdk/installing/studio-layout.html">
diff --git a/docs/html/training/app-indexing/index.jd b/docs/html/training/app-indexing/index.jd
index cb4135f..7e7241b 100644
--- a/docs/html/training/app-indexing/index.jd
+++ b/docs/html/training/app-indexing/index.jd
@@ -59,14 +59,6 @@
</li>
</ol>
-<p class="note"><strong>Note: </strong>
-Currently, the Google Search app indexing capability is restricted to
-English-only Android apps from developers participating in the early adopter
-program. You can sign up to be a participant by submitting the <a
-href="https://docs.google.com/a/google.com/forms/d/1itcqPAQqggJ6e4m8aejWLM8Dc5O8P6qybgGbKCNxGV0/viewform"
-class="external-link" target="_blank">App Indexing Expression of Interest</a> form.
-</p>
-
<p>This class shows how to enable deep linking and indexing of your application
content so that users can open this content directly from mobile search
results.</p>
diff --git a/docs/html/training/building-wearables.jd b/docs/html/training/building-wearables.jd
new file mode 100644
index 0000000..0745c93
--- /dev/null
+++ b/docs/html/training/building-wearables.jd
@@ -0,0 +1,10 @@
+page.title=Building Apps for Wearables
+page.trainingcourse=true
+page.image=wear/images/notifications.png
+page.metaDescription=Learn how to build notifications, send and sync data, and use voice actions.
+
+@jd:body
+
+
+<p>These classes teach you how to build notifications in a handheld app that are automatically
+synced to wearables as well as how to build apps that run on wearables.</p>
\ No newline at end of file
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index c5dc3c5..4407d30 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -638,7 +638,6 @@
</li>
</ul>
</li>
-
</ul>
</li>
<!-- End connectivity and cloud -->
@@ -727,6 +726,90 @@
<!-- End privacy and location -->
+ <li class="nav-section">
+ <div class="nav-section-header">
+ <a href="<?cs var:toroot ?>training/building-wearables.html">
+ <span class="small">Building Apps for</span><br/>
+ Wearables
+ </a>
+ </div>
+ <ul>
+ <li class="nav-section">
+ <div class="nav-section-header">
+ <a href="<?cs var:toroot ?>training/wearables/notifications/index.html"
+ description="How to build handheld notifications that are synced to
+ and look great on wearables."
+ >Adding Wearable Features to Notifications</a>
+ </div>
+ <ul>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/notifications/creating.html">Creating a Notification</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/notifications/voice-input.html">Receiving Voice Input in a Notification</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/notifications/pages.html">Adding Pages to a Notification</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/notifications/stacks.html">Stacking Notifications</a>
+ </li>
+ </ul>
+ </li>
+
+ <li class="nav-section">
+ <div class="nav-section-header">
+ <a href="<?cs var:toroot ?>training/wearables/apps/index.html"
+ description="How to build apps that run directly on wearables."
+ >Creating Wearable Apps</a>
+ </div>
+ <ul>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/apps/creating.html">Creating and Running a Wearable App</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/apps/layouts.html">Creating Custom Layouts</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/apps/voice.html">Adding Voice Capabilities</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/apps/packaging.html">Packaging Wearable Apps</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/apps/bt-debugging.html">Debugging over Bluetooth</a>
+ </li>
+ </ul>
+ </li>
+
+ <li class="nav-section">
+ <div class="nav-section-header">
+ <a href="<?cs var:toroot ?>training/wearables/data-layer/index.html"
+ description="How to sync data between handhelds and wearables."
+ >Sending and Syncing Data</a>
+ </div>
+ <ul>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/data-layer/accessing.html">Accessing the Wearable Data Layer</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/data-layer/data-items.html">Syncing Data Items</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/data-layer/assets.html">Transferring Assets</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/data-layer/messages.html">Sending and Receiving Messages</a>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>training/wearables/data-layer/events.html">Handling Data Layer Events</a>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+
+ <!-- End Building for wearables -->
<li class="nav-section">
<div class="nav-section-header">
diff --git a/docs/html/training/tv/index.jd b/docs/html/training/tv/index.jd
new file mode 100644
index 0000000..54f7016
--- /dev/null
+++ b/docs/html/training/tv/index.jd
@@ -0,0 +1,59 @@
+page.title=Designing for TV
+page.tags="input","screens"
+
+trainingnavtop=true
+startpage=true
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<!-- Required platform, tools, add-ons, devices, knowledge, etc. -->
+<h2>Dependencies and prerequisites</h2>
+<ul>
+ <li>Android 2.0 (API Level 5) or higher</li>
+</ul>
+
+</div>
+</div>
+
+<a class="notice-developers-video wide" href="http://www.youtube.com/watch?v=zsRnRLh-O34">
+<div>
+ <h3>Video</h3>
+ <p>DevBytes: Design for Large Displays - Part 1</p>
+</div>
+</a>
+
+<p>
+ Smart TVs powered by Android bring your favorite Android apps to the best screen in your house.
+ Thousands of apps in the Google Play Store are already optimized for TVs. This class shows how
+ you can optimize your Android app for TVs, including how to build a layout that
+ works great when the user is ten feet away and navigating with a remote control.
+</p>
+
+<h2>Lessons</h2>
+
+<dl>
+ <dt><b><a href="optimizing-layouts-tv.html">Optimizing Layouts for TV</a></b></dt>
+ <dd>Shows you how to optimize app layouts for TV screens, which have some unique characteristics such as:
+ <ul>
+ <li>permanent "landscape" mode</li>
+ <li>high-resolution displays</li>
+ <li>"10 foot UI" environment.</li>
+ </ul>
+ </dd>
+
+ <dt><b><a href="optimizing-navigation-tv.html">Optimizing Navigation for TV</a></b></dt>
+ <dd>Shows you how to design navigation for TVs, including:
+ <ul>
+ <li>handling D-pad navigation</li>
+ <li>providing navigational feedback</li>
+ <li>providing easily-accessible controls on the screen.</li>
+ </ul>
+ </dd>
+
+ <dt><b><a href="unsupported-features-tv.html">Handling features not supported on TV</a></b></dt>
+ <dd>Lists the hardware features that are usually not available on TVs. This lesson also shows you how to
+ provide alternatives for missing features or check for missing features and disable code at run time.</dd>
+</dl>
diff --git a/docs/html/training/tv/optimizing-layouts-tv.jd b/docs/html/training/tv/optimizing-layouts-tv.jd
new file mode 100644
index 0000000..a6db052
--- /dev/null
+++ b/docs/html/training/tv/optimizing-layouts-tv.jd
@@ -0,0 +1,246 @@
+page.title=Optimizing Layouts for TV
+parent.title=Designing for TV
+parent.link=index.html
+
+trainingnavtop=true
+next.title=Optimizing Navigation for TV
+next.link=optimizing-navigation-tv.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#DesignLandscapeLayouts">Design Landscape Layouts</a></li>
+ <li><a href="#MakeTextControlsEasyToSee">Make Text and Controls Easy to See</a></li>
+ <li><a href="#DesignForLargeScreens">Design for High-Density Large Screens</a></li>
+ <li><a href="#HandleLargeBitmaps">Design to Handle Large Bitmaps</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a></li>
+</ul>
+
+</div>
+</div>
+
+<p>
+When your application is running on a television set, you should assume that the user is sitting about
+ten feet away from the screen. This user environment is referred to as the
+<a href="http://en.wikipedia.org/wiki/10-foot_user_interface">10-foot UI</a>. To provide your
+users with a usable and enjoyable experience, you should style and lay out your UI accordingly..
+</p>
+<p>
+This lesson shows you how to optimize layouts for TV by:
+</p>
+<ul>
+ <li>Providing appropriate layout resources for landscape mode.</li>
+ <li>Ensuring that text and controls are large enough to be visible from a distance.</li>
+ <li>Providing high resolution bitmaps and icons for HD TV screens.</li>
+</ul>
+
+<h2 id="DesignLandscapeLayouts">Design Landscape Layouts</h2>
+
+<p>
+TV screens are always in landscape orientation. Follow these tips to build landscape layouts optimized for TV screens:
+</p>
+<ul>
+ <li>Put on-screen navigational controls on the left or right side of the screen and save the
+ vertical space for content.</li>
+ <li>Create UIs that are divided into sections, by using <a href="{@docRoot}guide/components/fragments.html">Fragments</a>
+ and use view groups like {@link android.widget.GridView} instead
+ of {@link android.widget.ListView} to make better use of the
+ horizontal screen space.</li>
+ <li>Use view groups such as {@link android.widget.RelativeLayout}
+ or {@link android.widget.LinearLayout} to arrange views.
+ This allows the Android system to adjust the position of the views to the size, alignment,
+ aspect ratio, and pixel density of the TV screen.</li>
+ <li>Add sufficient margins between layout controls to avoid a cluttered UI.</li>
+</ul>
+
+<p>
+For example, the following layout is optimized for TV:
+</p>
+
+<img src="{@docRoot}images/training/panoramio-grid.png" />
+
+<p>
+In this layout, the controls are on the lefthand side. The UI is displayed within a
+{@link android.widget.GridView}, which is well-suited to landscape orientation.
+In this layout both GridView and Fragment have the width and height set
+dynamically, so they can adjust to the screen resolution. Controls are added to the left side Fragment programatically at runtime.
+The layout file for this UI is {@code res/layout-land-large/photogrid_tv.xml}.
+(This layout file is placed in {@code layout-land-large} because TVs have large screens with landscape orientation. For details refer to
+<a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a>.)</p>
+
+res/layout-land-large/photogrid_tv.xml
+<pre>
+<RelativeLayout
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" >
+
+ <fragment
+ android:id="@+id/leftsidecontrols"
+ android:layout_width="0dip"
+ android:layout_marginLeft="5dip"
+ android:layout_height="match_parent" />
+
+ <GridView
+ android:id="@+id/gridview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</RelativeLayout>
+</pre>
+
+<p>
+To set up action bar items on the left side of the screen, you can also include the <a
+href="http://code.google.com/p/googletv-android-samples/source/browse/#git%2FLeftNavBarLibrary">
+Left navigation bar library</a> in your application to set up action items on the left side
+of the screen, instead of creating a custom Fragment to add controls:
+</p>
+
+<pre>
+LeftNavBar bar = (LeftNavBarService.instance()).getLeftNavBar(this);
+</pre>
+
+<p>
+When you have an activity in which the content scrolls vertically, always use a left navigation bar;
+otherwise, your users have to scroll to the top of the content to switch between the content view and
+the ActionBar. Look at the
+<a href="http://code.google.com/p/googletv-android-samples/source/browse/#git%2FLeftNavBarDemo">
+Left navigation bar sample app</a> to see how to simple it is to include the left navigation bar in your app.
+</p>
+
+<h2 id="MakeTextControlsEasyToSee">Make Text and Controls Easy to See</h2>
+<p>
+The text and controls in a TV application's UI should be easily visible and navigable from a distance.
+Follow these tips to make them easier to see from a distance :
+</p>
+
+<ul>
+ <li>Break text into small chunks that users can quickly scan.</li>
+ <li>Use light text on a dark background. This style is easier to read on a TV.</li>
+ <li>Avoid lightweight fonts or fonts that have both very narrow and very broad strokes. Use simple sans-serif
+ fonts and use anti-aliasing to increase readability.</li>
+ <li>Use Android's standard font sizes:
+ <pre>
+ <TextView
+ android:id="@+id/atext"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"/>
+ </pre></li>
+ <li>Ensure that all your view widgets are large enough to be clearly visible to someone sitting 10 feet away
+ from the screen (this distance is greater for very large screens). The best way to do this is to use
+ layout-relative sizing rather than absolute sizing, and density-independent pixel units instead of absolute
+ pixel units. For example, to set the width of a widget, use wrap_content instead of a pixel measurement,
+ and to set the margin for a widget, use dip instead of px values.
+ </li>
+</ul>
+<p>
+
+</p>
+
+<h2 id="DesignForLargeScreens">Design for High-Density Large Screens</h2>
+
+<p>
+The common HDTV display resolutions are 720p, 1080i, and 1080p. Design your UI for 1080p, and then
+allow the Android system to downscale your UI to 720p if necessary. In general, downscaling (removing pixels)
+does not degrade the UI (Notice that the converse is not true; you should avoid upscaling because it degrades
+UI quality).
+</p>
+
+<p>
+To get the best scaling results for images, provide them as <a href="{@docRoot}tools/help/draw9patch.html">
+9-patch image</a> elements if possible.
+If you provide low quality or small images in your layouts, they will appear pixelated, fuzzy, or grainy. This
+is not a good experience for the user. Instead, use high-quality images.
+</p>
+
+<p>
+For more information on optimizing apps for large screens see <a href="{@docRoot}training/multiscreen/index.html">
+Designing for multiple screens</a>.
+</p>
+
+<h2 id="HandleLargeBitmaps">Design to Handle Large Bitmaps</h2>
+
+<p>
+The Android system has a limited amount of memory, so downloading and storing high-resolution images can often
+cause out-of-memory errors in your app. To avoid this, follow these tips:
+</p>
+
+<ul>
+ <li>Load images only when they're displayed on the screen. For example, when displaying multiple images in
+ a {@link android.widget.GridView} or
+ {@link android.widget.Gallery}, only load an image when
+ {@link android.widget.Adapter#getView(int, View, ViewGroup) getView()}
+ is called on the View's {@link android.widget.Adapter}.
+ </li>
+ <li>Call {@link android.graphics.Bitmap#recycle()} on
+ {@link android.graphics.Bitmap} views that are no longer needed.
+ </li>
+ <li>Use {@link java.lang.ref.WeakReference} for storing references
+ to {@link android.graphics.Bitmap} objects in an in-memory
+ {@link java.util.Collection}.</li>
+ <li>If you fetch images from the network, use {@link android.os.AsyncTask}
+ to fetch them and store them on the SD card for faster access.
+ Never do network transactions on the application's UI thread.
+ </li>
+ <li>Scale down really large images to a more appropriate size as you download them; otherwise, downloading the image
+ itself may cause an "Out of Memory" exception. Here is sample code that scales down images while downloading:
+
+ <pre>
+ // Get the source image's dimensions
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ // This does not download the actual image, just downloads headers.
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(IMAGE_FILE_URL, options);
+ // The actual width of the image.
+ int srcWidth = options.outWidth;
+ // The actual height of the image.
+ int srcHeight = options.outHeight;
+
+ // Only scale if the source is bigger than the width of the destination view.
+ if(desiredWidth > srcWidth)
+ desiredWidth = srcWidth;
+
+ // Calculate the correct inSampleSize/scale value. This helps reduce memory use. It should be a power of 2.
+ int inSampleSize = 1;
+ while(srcWidth / 2 > desiredWidth){
+ srcWidth /= 2;
+ srcHeight /= 2;
+ inSampleSize *= 2;
+ }
+
+ float desiredScale = (float) desiredWidth / srcWidth;
+
+ // Decode with inSampleSize
+ options.inJustDecodeBounds = false;
+ options.inDither = false;
+ options.inSampleSize = inSampleSize;
+ options.inScaled = false;
+ // Ensures the image stays as a 32-bit ARGB_8888 image.
+ // This preserves image quality.
+ options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+
+ Bitmap sampledSrcBitmap = BitmapFactory.decodeFile(IMAGE_FILE_URL, options);
+
+ // Resize
+ Matrix matrix = new Matrix();
+ matrix.postScale(desiredScale, desiredScale);
+ Bitmap scaledBitmap = Bitmap.createBitmap(sampledSrcBitmap, 0, 0,
+ sampledSrcBitmap.getWidth(), sampledSrcBitmap.getHeight(), matrix, true);
+ sampledSrcBitmap = null;
+
+ // Save
+ FileOutputStream out = new FileOutputStream(LOCAL_PATH_TO_STORE_IMAGE);
+ scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
+ scaledBitmap = null;
+ </pre>
+ </li> </ul>
\ No newline at end of file
diff --git a/docs/html/training/tv/optimizing-navigation-tv.jd b/docs/html/training/tv/optimizing-navigation-tv.jd
new file mode 100644
index 0000000..bb78258
--- /dev/null
+++ b/docs/html/training/tv/optimizing-navigation-tv.jd
@@ -0,0 +1,206 @@
+page.title=Optimizing Navigation for TV
+parent.title=Designing for TV
+parent.link=index.html
+
+trainingnavtop=true
+previous.title=Optimizing Layouts for TV
+previous.link=optimizing-layouts-tv.html
+next.title=Handling Features Not Supported on TV
+next.link=unsupported-features-tv.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#HandleDpadNavigation">Handle D-pad Navigation</a></li>
+ <li><a href="#HandleFocusSelection">Provide Clear Visual Indication for Focus and Selection</a></li>
+ <li><a href="#DesignForEasyNavigation">Design for Easy Navigation</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}training/design-navigation/index.html">Designing Effective Navigation</a></li>
+</ul>
+
+</div>
+</div>
+
+<p>
+An important aspect of the user experience when operating a TV is the direct human interface: a remote control.
+As you optimize your Android application for TVs, you should pay special attention to how the user actually navigates
+around your application when using a remote control instead of a touchscreen.
+</p>
+<p>
+This lesson shows you how to optimize navigation for TV by:
+</p>
+
+<ul>
+ <li>Ensuring all layout controls are D-pad navigable.</li>
+ <li>Providing highly obvious feedback for UI navigation.</li>
+ <li>Placing layout controls for easy access.</li>
+</ul>
+
+<h2 id="HandleDpadNavigation">Handle D-pad Navigation</h2>
+
+<p>
+On a TV, users navigate with controls on a TV remote, using either a D-pad or arrow keys.
+This limits movement to up, down, left, and right.
+To build a great TV-optimized app, you must provide a navigation scheme in which the user can
+quickly learn how to navigate your app using the remote.
+</p>
+
+<p>
+When you design navigation for D-pad, follow these guidelines:
+</p>
+
+<ul>
+ <li>Ensure that the D-pad can navigate to all the visible controls on the screen.</li>
+ <li>For scrolling lists with focus, D-pad up/down keys scroll the list and Enter key selects an item in the list. Ensure that users can
+ select an element in the list and that the list still scrolls when an element is selected.</li>
+ <li>Ensure that movement between controls is straightforward and predictable.</li>
+</ul>
+
+<p>
+Android usually handles navigation order between layout elements automatically, so you don't need to do anything extra. If the screen layout
+makes navigation difficult, or if you want users to move through the layout in a specific way, you can set up explicit navigation for your
+controls.
+For example, for an {@code android.widget.EditText}, to define the next control to receive focus, use:
+<pre>
+<EditText android:id="@+id/LastNameField" android:nextFocusDown="@+id/FirstNameField"\>
+</pre>
+The following table lists all of the available navigation attributes:
+</p>
+
+<table>
+<tr>
+<th>Attribute</th>
+<th>Function</th>
+</tr>
+<tr>
+<td>{@link android.R.attr#nextFocusDown}</td>
+<td>Defines the next view to receive focus when the user navigates down.</td>
+</tr>
+<tr>
+<td>{@link android.R.attr#nextFocusLeft}</td>
+<td>Defines the next view to receive focus when the user navigates left.</td>
+</tr>
+<tr>
+<td>{@link android.R.attr#nextFocusRight}</td>
+<td>Defines the next view to receive focus when the user navigates right.</td>
+</tr>
+<tr>
+<td>{@link android.R.attr#nextFocusUp}</td>
+<td>Defines the next view to receive focus when the user navigates up.</td>
+</tr>
+</table>
+
+<p>
+To use one of these explicit navigation attributes, set the value to the ID (android:id value) of another widget in the layout. You should set
+up the navigation order as a loop, so that the last control directs focus back to the first one.
+</p>
+
+<p>
+Note: You should only use these attributes to modify the navigation order if the default order that the system applies does not work well.
+</p>
+
+<h2 id="HandleFocusSelection">Provide Clear Visual Indication for Focus and Selection</h2>
+
+<p>
+Use appropriate color highlights for all navigable and selectable elements in the UI. This makes it easy for users to know whether the control
+is currently focused or selected when they navigate with a D-pad. Also, use uniform highlight scheme across your application.
+</p>
+
+<p>
+Android provides <a href="{@docRoot}guide/topics/resources/drawable-resource.html#StateList">Drawable State List Resources</a> to implement highlights
+for selected and focused controls. For example:
+</p>
+
+res/drawable/button.xml:
+<pre>
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true"
+ android:drawable="@drawable/button_pressed" /> <!-- pressed -->
+ <item android:state_focused="true"
+ android:drawable="@drawable/button_focused" /> <!-- focused -->
+ <item android:state_hovered="true"
+ android:drawable="@drawable/button_focused" /> <!-- hovered -->
+ <item android:drawable="@drawable/button_normal" /> <!-- default -->
+</selector>
+</pre>
+
+<p>
+This layout XML applies the above state list drawable to a {@link android.widget.Button}:
+</p>
+<pre>
+<Button
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:background="@drawable/button" />
+</pre>
+
+<p>
+Provide sufficient padding within the focusable and selectable controls so that the highlights around them are clearly visible.
+</p>
+
+<h2 id="DesignForEasyNavigation">Design for Easy Navigation</h2>
+
+<p>
+Users should be able to navigate to any UI control with a couple of D-pad clicks. Navigation should be easy and intuitive to
+understand. For any non-intuitive actions, provide users with written help, using a dialog triggered by a help button or action bar icon.
+</p>
+
+<p>
+Predict the next screen that the user will want to navigate to and provide one click navigation to it. If the current screen UI is very sparse,
+consider making it a multi pane screen. Use fragments for making multi-pane screens. For example, consider the multi-pane UI below with continent names
+on the left and list of cool places in each continent on the right.
+</p>
+
+<img src="{@docRoot}images/training/cool-places.png" alt="" />
+
+<p>
+The above UI consists of three Fragments - <code>left_side_action_controls</code>, <code>continents</code> and
+<code>places</code> - as shown in its layout
+xml file below. Such multi-pane UIs make D-pad navigation easier and make good use of the horizontal screen space for
+TVs.
+</p>
+res/layout/cool_places.xml
+<pre>
+<LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ >
+ <fragment
+ android:id="@+id/left_side_action_controls"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="10dip"
+ android:layout_weight="0.2"/>
+ <fragment
+ android:id="@+id/continents"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="10dip"
+ android:layout_weight="0.2"/>
+
+ <fragment
+ android:id="@+id/places"
+ android:layout_width="0px"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="10dip"
+ android:layout_weight="0.6"/>
+
+</LinearLayout>
+</pre>
+
+<p>
+Also, notice in the UI layout above action controls are on the left hand side of a vertically scrolling list to make
+them easily accessible using D-pad.
+In general, for layouts with horizontally scrolling components, place action controls on left or right hand side and
+vice versa for vertically scrolling components.
+</p>
+
diff --git a/docs/html/training/tv/unsupported-features-tv.jd b/docs/html/training/tv/unsupported-features-tv.jd
new file mode 100644
index 0000000..a9f090b
--- /dev/null
+++ b/docs/html/training/tv/unsupported-features-tv.jd
@@ -0,0 +1,157 @@
+page.title=Handling Features Not Supported on TV
+parent.title=Designing for TV
+parent.link=index.html
+
+trainingnavtop=true
+previous.title=Optimizing Navigation for TV
+previous.link=optimizing-navigation-tv.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#WorkaroundUnsupportedFeatures">Work Around Features Not Supported on TV</a></li>
+ <li><a href="#CheckAvailableFeatures">Check for Available Features at Runtime</a></li>
+</ol>
+
+</div>
+</div>
+
+<p>
+TVs are much different from other Android-powered devices:
+</p>
+<ul>
+ <li>They're not mobile.</li>
+ <li>Out of habit, people use them for watching media with little or no interaction.</li>
+ <li>People interact with them from a distance.</li>
+</ul>
+
+<p>
+Because TVs have a different purpose from other devices, they usually don't have hardware features
+that other Android-powered devices often have. For this reason, the Android system does not
+support the following features for a TV device:
+<table>
+<tr>
+<th>Hardware</th>
+<th>Android feature descriptor</th>
+</tr>
+<tr>
+<td>Camera</td>
+<td>android.hardware.camera</td>
+</tr>
+<tr>
+<td>GPS</td>
+<td>android.hardware.location.gps</td>
+</tr>
+<tr>
+<td>Microphone</td>
+<td>android.hardware.microphone</td>
+</tr>
+<tr>
+<td>Near Field Communications (NFC)</td>
+<td>android.hardware.nfc</td>
+</tr>
+<tr>
+<td>Telephony</td>
+<td>android.hardware.telephony</td>
+</tr>
+<tr>
+<td>Touchscreen</td>
+<td>android.hardware.touchscreen</td>
+</tr>
+</table>
+</p>
+
+<p>
+This lesson shows you how to work around features that are not available on TV by:
+<ul>
+ <li>Providing work arounds for some non-supported features.</li>
+ <li>Checking for available features at runtime and conditionally activating/deactivating certain code
+ paths based on availability of those features.</li>
+</ul>
+</p>
+
+
+<h2 id="WorkaroundUnsupportedFeatures">Work Around Features Not Supported on TV</h2>
+
+<p>
+Android doesn't support touchscreen interaction for TV devices, most TVs don't have touch screens,
+and interacting with a TV using a touchscreen is not consistent with the 10 foot environment. For
+these reasons, users interact with Android-powered TVs using a remote. In consideration of this,
+ensure that every control in your app can be accessed with the D-pad. Refer back to the previous two lessons
+<a href="{@docRoot}training/tv/optimizing-layouts-tv.html">Optimizing Layouts for TV</a> and
+<a href="{@docRoot}training/tv/optimizing-navigation-tv.html">Optimize Navigation for TV</a> for
+more details
+on this topic. The Android system assumes that a device has a touchscreen, so if you want your application
+to run on a TV, you must <strong>explicitly</strong> disable the touchscreen requirement in your manifest file:
+<pre>
+<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
+</pre>
+</p>
+
+<p>
+Although a TV doesn't have a camera, you can still provide a photography-related application on a TV.
+For example, if you have an app that takes, views and edits photos, you can disable its picture-taking
+functionality for TVs and still allow users to view and even edit photos. The next section talks about how to
+deactivate or activate specific functions in the application based on runtime device type detection.
+</p>
+
+<p>
+Because TVs are stationary, indoor devices, they don't have built-in GPS. If your application uses location
+information, allow users to search for a location or use a "static" location provider to get
+a location from the zip code configured during the TV setup.
+<pre>
+LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
+Location location = locationManager.getLastKnownLocation("static");
+Geocoder geocoder = new Geocoder(this);
+Address address = null;
+
+try {
+ address = geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1).get(0);
+ Log.d("Zip code", address.getPostalCode());
+
+} catch (IOException e) {
+ Log.e(TAG, "Geocoder error", e);
+}
+</pre>
+</p>
+
+<p>
+TVs usually don't support microphones, but if you have an application that uses voice control,
+you can create a mobile device app that takes voice input and then acts as a remote control for a TV.
+</p>
+
+<h2 id="CheckAvailableFeatures">Check for Available Features at Runtime</h2>
+
+<p>
+To check if a feature is available at runtime, call
+{@link android.content.pm.PackageManager#hasSystemFeature(String)}.
+ This method takes a single argument : a string corresponding to the
+feature you want to check. For example, to check for touchscreen, use
+{@link android.content.pm.PackageManager#hasSystemFeature(String)} with the argument
+{@link android.content.pm.PackageManager#FEATURE_TOUCHSCREEN}.
+</p>
+
+<p>
+The following code snippet demonstrates how to detect device type at runtime based on supported features:
+
+<pre>
+// Check if android.hardware.telephony feature is available.
+if (getPackageManager().hasSystemFeature("android.hardware.telephony")) {
+ Log.d("Mobile Test", "Running on phone");
+// Check if android.hardware.touchscreen feature is available.
+} else if (getPackageManager().hasSystemFeature("android.hardware.touchscreen")) {
+ Log.d("Tablet Test", "Running on devices that don't support telphony but have a touchscreen.");
+} else {
+ Log.d("TV Test", "Running on a TV!");
+}
+</pre>
+</p>
+
+<p>
+This is just one example of using runtime checks to deactivate app functionality that depends on features
+that aren't available on TVs.
+</p>
\ No newline at end of file
diff --git a/docs/html/training/wearables/apps/bt-debugging.jd b/docs/html/training/wearables/apps/bt-debugging.jd
new file mode 100644
index 0000000..8d09c43
--- /dev/null
+++ b/docs/html/training/wearables/apps/bt-debugging.jd
@@ -0,0 +1,92 @@
+page.title=Debugging over Bluetooth
+
+@jd:body
+
+<div id="tb-wrapper">
+ <div id="tb">
+
+ <!-- Required platform, tools, add-ons, devices, knowledge, etc. -->
+ <h2>This lesson teaches you to</h2>
+ <ol>
+ <li><a href="#SetupDevices">Set Up Devices for Debugging</a></li>
+ <li><a href="#SetupSession">Set Up a Debugging Session</a></li>
+ <li><a href="#DebugApp">Debug Your App</a></li>
+ </ol>
+ <h2>You should also read</h2>
+ <ul>
+ <li><a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a></li>
+ </ul>
+ </div>
+</div>
+
+<p>You can debug your wearable over Bluetooth by routing it's debug output to the
+handheld device that's connected to your development machine.</p>
+
+<h2 id="SetupDevices">Setup Devices for Debugging</h2>
+<ol>
+ <li>Enable USB debugging on the handheld:
+ <ul>
+ <li>Open the Settings app and scroll to the bottom.</li>
+ <li>If it doesn't have a Developer Options setting, tap <b>About Phone</b>
+ (or <b>About Tablet</b>), scroll to the bottom, and tap the build number 7 times.</li>
+ <li>Go back and tap <b>Developer Options</b>.</li>
+ <li>Enable <b>USB debugging</b>.</li>
+ </ul>
+ </li>
+ <li>Enable Bluetooth debugging on the wearable:
+ <ol>
+ <li>Tap the home screen twice to bring up the Wear menu. </li>
+ <li>Scroll to the bottom and tap <b>Settings</b>.</li>
+ <li>Scroll to the bottom. If there's no <b>Developer Options</b> item, tap <b>About</b>,
+ and then tap the build number 7 times.</li>
+ <li>Tap the <b>Developer Options</b> item.</li>
+ <li>Enable <b>Debug over Bluetooth</b>.</li>
+ </ol>
+ </li>
+</ol>
+
+<h2 id="SetupSession">Set Up a Debugging Session</h2>
+<ol>
+ <li>On the handheld, open the Android Wear companion app.</li>
+ <li>Tap the menu on the top right and select <b>Settings</b>.</li>
+ <li>Enable <b>Debugging over Bluetooth</b>. You should see a tiny status summary appear under the
+ option:
+ <pre>
+Host: disconnected
+Target: connected
+</pre>
+</li>
+<li>Connect the handheld to your machine over USB and run:
+<pre>
+adb forward tcp:4444 localabstract:/adb-hub; adb connect localhost:4444
+</pre>
+
+<p class="note"><b>Note</b>: You can use any available port that you have access to.</p>
+</li>
+</ol>
+<p>
+In the Android Wear companion app, you should see the status change to:</p>
+<pre>
+Host: connected
+Target: connected
+</pre>
+
+<h2 id="#debug">Debug Your App</h2>
+
+Your wearable should show up as <code>localhost:4444</code> when running <code>adb devices</code>.
+
+To run any <code>adb</code> command, use this format:
+
+<pre>adb -s localhost:4444 <command> </pre>
+
+<p>If there are no other devices connected over TCP/IP (namely emulators), you can shorten the command
+to:</p>
+<pre>
+adb -e <command>
+</pre>
+<p>For example:</p>
+<pre>
+adb -e logcat
+adb -e shell
+adb -e bugreport
+</pre>
\ No newline at end of file
diff --git a/docs/html/training/wearables/apps/creating.jd b/docs/html/training/wearables/apps/creating.jd
new file mode 100644
index 0000000..a5107d7
--- /dev/null
+++ b/docs/html/training/wearables/apps/creating.jd
@@ -0,0 +1,184 @@
+page.title=Creating and Running a Wearable App
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#SetupEmulator">Set Up an Android Wear Emulator</a></li>
+ <li><a href="#SetupDevice">Set Up an Android Wear Device</a></li>
+ <li><a href="#CreateProject">Create a Project</a></li>
+ <li><a href="#Libraries">Include the Correct Libraries</a></li>
+</ol>
+<h2>Dependencies and Prerequisites</h2>
+ <ul>
+ <li>Android Studio 0.8 or later and Gradle 0.12 or later</li>
+ </ul>
+</div>
+</div>
+
+<p>Wearable apps run directly on the wearable device, giving you access to low-level
+hardware such as sensors, activities, services, and more, right
+on the wearable.</p>
+
+<p>A companion handheld app that contains the
+wearable app is also required when you want to publish to the Google Play store.
+Wearables don't support the Google Play store, so users download the companion handheld app,
+which automatically pushes the wearable app to the wearable. The handheld app is also
+useful for doing heavy processing, network actions, or other work and
+sending the results to the wearable.
+</p>
+
+<p>This lesson goes over how to set up a device or emulator and create one project to contain
+both your wearable and handheld apps.
+</p>
+
+
+<h2 id="SetupEmulator">Set Up an Android Wear Emulator or Device</h2>
+<p>We recommend that you develop on real hardware so you can better
+gauge the user experience. However, the emulator lets you test out different
+types of screen shapes, which is useful for testing.</p>
+
+<h3>Set up an Android Wear Virtual Device</h3>
+
+<p>To set up an Android Wear virtual device:</p>
+<ol>
+ <li>Click <b>Tools > Android > AVD Manager</b>.</li>
+ <li>Click <b>Create...</b>.</li>
+ <li>Fill in the following details for the AVD you want to specify and leave the rest
+ of the fields with their default values:
+ <ol>
+ <li><b>AVD Name</b> - A name for your AVD</li>
+ <li><b>Device</b> - Android Wear Round or Square device types</li>
+ <li><b>Target</b> - Android 4.4W - API Level 20</li>
+ <li><b>CPU/ABI</b> - Android Wear ARM (armeabi-v7a)</li>
+ <li><b>Keyboard</b> - Select <b>Hardware keyboard present</b></li>
+ <li><b>Skin</b> - AndroidWearRound or AndroidWearSquare depending on the selected device type</li>
+ <li><b>Snapshot</b> - Not selected</li>
+ <li><b>Use Host GPU</b> - Selected, to support custom activities for wearable notifications</li>
+ </ol>
+ </li>
+ <li>Click <b>OK</b>.</li>
+<li>Start the emulator:
+<ol>
+ <li>Select the virtual device you just created.</li>
+ <li>Click <b>Start...</b>, then click <b>Launch</b>.</li>
+ <li>Wait until the emulator initializes and shows the Android Wear home screen.</li>
+</ol>
+</li>
+<li>Pair Your handheld with the emulator:
+<ol>
+ <li>On your handheld, install the Android Wear app from Google Play.</li>
+ <li>Connect the handheld to your machine through USB.</li>
+ <li>Forward the AVD's communication port to the connected handheld device (you must
+ do this every time the handheld is connected):
+ <pre>adb -d forward tcp:5601 tcp:5601</pre>
+ </li>
+ <li>Start the Android Wear app on your handheld device and connect to the emulator.</li>
+ <li>Tap the menu on the top right corner of the Android Wear app and select
+ <b>Demo Cards</b>.</li>
+ <li>The cards you select appear as notifications on the home screen of the emulator.</li>
+</ol>
+</li>
+</ol>
+
+<h3 id="SetupDevice">Set Up an Android Wear Device</h3>
+<p>To set up an Android Wear device:</p>
+<ol>
+ <li>Install the Android Wear app, available on Google Play, on your handheld.</li>
+ <li>Follow the app's instructions to pair your handheld with your wearable.
+ This allows you to test out synced handheld notifications, if you're building them.</li>
+ <li>Connect the wearable to your machine through USB, so you can install apps directly to it
+ as you develop.</li>
+</ol>
+
+<h2 id="CreateProject">Create a Project</h2>
+
+<p>To begin development, create an app project that contains
+ wearable and handheld app modules. In Android Studio, click <b>File</b> >
+ <b>New Project</b> and follow the Project Wizard instructions, as described in
+ <a href="{@docRoot}sdk/installing/create-project.html">Creating a
+Project</a>. As you follow the wizard, enter the following information:</p>
+
+<ol>
+ <li>In the <b>Configure your Project</b> window, enter a name for your app and a package
+ name.</li>
+ <li>In the <b>Form Factors</b> window:
+ <ul>
+ <li>Select <b>Phone and Tablet</b> and select <b>API 8: Android 2.2 (Froyo)</b>
+ under <b>Minimum SDK</b>.</li>
+ <li>Select <b>Wear</b> and select <b>API 20: Android 4.4 (KitKat Wear)</b>
+ under <b>Minimum SDK</b>.</li>
+ </ul>
+ </li>
+ <li>In the first <b>Add an Activity</b> window, add a blank activity for mobile.</li>
+ <li>In the second <b>Add an Activity</b> window, add a blank activity for Wear.
+
+ <p>When the wizard completes, Android Studio creates a new project with two modules, <b>mobile</b> and
+ <b>wear</b>. You now have a project for both your handheld and wearable apps that you can create activities,
+ services, custom layouts, and much more in. On the handheld app, you do most of the heavy lifting,
+ such as network communications, intensive processing, or tasks that require long
+ amounts of user interaction. When these are done,
+ you usually notify the wearable of the results through notifications or by syncing and sending
+ data to the wearable.</p>
+
+ <p class="note"><b>Note:</b> The <b>wear</b> module also contains a "Hello World" activity that uses a
+ <code>WatchViewStub</code> that inflates a layout based on whether the device's screen
+ is round or square. The <code>WatchViewStub</code> class is one of the UI widgets that's provided
+ by the <a href="{@docRoot}training/wearables/apps/layouts.html#UiLibrary">wearable support library</a>.</p>
+</li>
+
+<h2 id="Install">Install the Wearable app</h2>
+
+<p>When developing, you install apps directly to the wearable like with handheld apps. Use
+either <code>adb install</code> or the <b>Play</b> button on Android Studio.</p>
+
+<p>When you're ready to publish your app to users, you embed the wearable app inside of the
+handheld app. When users install the handheld app from Google Play, a connected wearable automatically
+receives the wearable app.</p>
+
+<p class="note"><b>Note:</b> The automatic installation of wearable apps
+does not work when you are signing apps with a debug key and only works with release keys. See
+<a href="{@docRoot}traiing/wearables/packaging.html">Packaging Wearable Apps</a> for
+complete information on how to properly package wearable apps.</p>
+
+<li>
+To install the "Hello World" app to the wearable, select <b>wear</b> from the <b>Run/Debug
+configuration</b> drop-down menu and click the <b>Play</b> button. The activity shows up on the
+wearable and prints out "Hello world!"
+</li></ol>
+<h2 id="Libraries">Include the Correct Libraries</h2>
+
+<p>As part of the Project Wizard, the correct
+dependencies are imported for you in the appropriate module's <code>build.gradle</code> file.
+However, these dependencies are not required, so read the following descriptions to find out if you need them or not:</p>
+
+<b>Notifications</b>
+<p>The <a href="{@docRoot}tools/support-library/features.html#v4">The
+Android v4 support library</a> (or v13, which includes v4)
+contains the APIs to extend your existing notifications on handhelds to support wearables.</p>
+
+<p>For notifications that appear only on
+the wearable (meaning, they are issued by an app that runs on the wearable), you can just use the
+standard framework APIs (API Level 20) on the wearable and remove the support library
+dependency in the <b>mobile</b> module of your project.
+</p>
+
+<b>Wearable Data Layer</b>
+<p>To sync and send data between wearables and handhelds with the Wearable Data Layer APIs,
+you need the latest version of
+<a href="{@docRoot}google/play-services/setup.html">Google Play services</a>.
+If you're not using these APIs, remove the dependency from both modules.</p>
+
+<b>Wearable UI support library</b>
+<p>This is an unofficial library that includes UI widgets designed for wearables. We encourage you
+to use them in your apps, because they exemplify best practices, but they can still
+change at any time. However, if the libraries are updated, your apps won't break since they are compiled
+into your app. To get new features from an updated library, you just need to
+statically link the new version and update your app accordingly.
+This library is only applicable if you create wearable apps.
+</p>
+
+<p>In the next lessons, you'll learn how to create layouts designed for wearables as well as how
+to use the various voice actions that are supported by the platform.</p>
diff --git a/docs/html/training/wearables/apps/index.jd b/docs/html/training/wearables/apps/index.jd
new file mode 100644
index 0000000..a0d02fb
--- /dev/null
+++ b/docs/html/training/wearables/apps/index.jd
@@ -0,0 +1,74 @@
+page.title=Creating Wearable Apps
+page.image=wear/images/notifications.png
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+ <h2>Dependencies and Prerequisites</h2>
+ <ul>
+ <li>Android Studio 0.8 or later and Gradle 0.12 or later</li>
+ </ul>
+</div>
+</div>
+
+<p>
+Wearable apps run directly on the device, giving you access to hardware such as sensors and the
+GPU. They are fundamentally the same as apps built for other devices using the Android SDK, but
+differ greatly in design and usability and the amount of functionality provided.
+These are the main differences between handheld and wearable apps:</p>
+
+<ul>
+ <li>The system enforces a timeout period. If you are displaying an activity and user's don't
+ interact with it, the device sleeps. When it wakes back up, the Wear home screen is displayed
+ instead of your activity. If you need to show something persistent, create a notification in the
+ context stream instead.</li>
+ <li>Wearable apps are relatively small in size and functionality compared to handheld apps.
+ They contain only what makes sense on the wearable, which is usually a small
+ subset of the corresponding handheld app. In general, you should carry out operations on the
+ handheld when possible and send the results to the wearable.
+ </li>
+ <li>Users don't download apps directly onto the wearable. Instead, you bundle
+ the wearable app inside the handheld app. When users install the handheld app,
+ the system automatically installs the wearable app. However, for development
+ purposes, you can still install the wearable app directly to the wearable.</li>
+ <li><p>Wearable apps can access much of the standard Android APIs, but don't support
+ the following APIs:</p>
+ <ul>
+ <li>{@link android.webkit}</li>
+ <li>{@link android.print}</li>
+ <li>{@link android.app.backup}</li>
+ <li>{@link android.appwidget}</li>
+ <li>{@link android.hardware.usb}</li>
+ </ul>
+ <p>
+ You can check if a wearable supports a feature by calling
+ {@link android.content.pm.PackageManager#hasSystemFeature hasSystemFeature()}
+ before trying to use an API.</p>
+</li>
+</ul>
+
+<p class="note"><b>Note:</b> We recommend using Android Studio for Android Wear development
+as it provides project setup, library inclusion, and packaging conveniences that aren't available
+in ADT. The rest of this training assumes you're using Android Studio.
+</p>
+
+<h2>Lessons</h2>
+ <dl>
+ <dt><a href="{@docRoot}training/wearables/apps/creating.html">Creating and Running a Wearable App</a></dt>
+ <dd>Learn how to create an Android Studio project that
+ contains both the wearable and handheld app modules and how to run the app on a device
+ or emulator.</dd>
+ <dt><a href="{@docRoot}training/wearables/apps/layouts.html">Creating Custom Layouts</a></dt>
+ <dd>Learn how to create and display custom layouts for notifications and
+ activities.</dd>
+ <dt><a href="{@docRoot}training/wearables/apps/voice.html">Adding Voice Capabilities</a></dt>
+ <dd>Learn how to launch an activity with a voice actions and how to start the
+ system speech recognizer app to obtain free-form voice input.</dd>
+ <dt><a href="{@docRoot}training/wearables/apps/packaging.html">Packaging Wearable Apps</a></dt>
+ <dd>Learn how to package a wearable app inside a
+ handheld app. This allows the system to install the wearable app automatically when
+ users install the companion handheld app from the Google Play store.</dd>
+ <dt><a href="{@docRoot}training/wearables/apps/packaging.html">Debugging over Bluetooth</a></dt>
+ <dd>Learn how to debug your wearable over Bluetooth instead of USB.</dd>
+ </dl>
\ No newline at end of file
diff --git a/docs/html/training/wearables/apps/layouts.jd b/docs/html/training/wearables/apps/layouts.jd
new file mode 100644
index 0000000..4bf9cde
--- /dev/null
+++ b/docs/html/training/wearables/apps/layouts.jd
@@ -0,0 +1,134 @@
+page.title=Creating Custom Layouts
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#CustomNotifications">Create custom notifications</a></li>
+ <li><a href="#UiLibrary">Create Layouts with the Wearable UI Library</li>
+</ol>
+
+<!--STOPSHIP: link these -->
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a></li>
+</ul>
+
+</div>
+</div>
+
+<p>Creating layouts for wearables is the same as handheld devices, except you have to design
+for the screen size and for glanceability. Do not port functionality
+and the UI from a handheld app and expect a good experience. You should create custom layouts
+only when necessary. Read the <a href="{@docRoot}design/wear/index.html">design guidelines</a>
+for information on how to design great wearable apps.</p>
+
+<h2 id="CustomNotifications">Create Custom Notifications</h2>
+
+<p>
+In general, you should create notifications on the handheld and let them
+automatically sync to the wearable. This lets you build your notifications
+once and have them appear on many types of devices (not just wearables, but
+eventually Auto and TV) without having to design them for different
+form factors.</p>
+
+<p>If the standard notification styles don't work for you (such as
+{@link android.support.v4.app.NotificationCompat.BigTextStyle} or
+{@link android.support.v4.app.NotificationCompat.InboxStyle}), you can display an activity with
+a custom layout. You can only create and issue custom notifications on the wearable, and the
+system does not sync these notifications to the handheld.</p>
+
+<p clas="note"><b>Note:</b> When creating custom notifications on the wearable, you can use the
+standard notification APIs (API Level 20) instead of the Support Library.
+</p>
+
+<p>To create a custom notification:</p>
+<ol>
+ <li>Create a layout and set it as the content view for the activity
+ that you want to display.
+<pre>
+public void onCreate(Bundle bundle){
+ ...
+ setContentView(R.layout.notification_activity);
+}
+</pre>
+ </li>
+ <li>Define necessary properties for the activity in the Android manifest to allow
+ the activity to be displayed in the wearable's context stream process. You need to declare the
+ activity to be exportable, be embeddable, and have an empty task affinity. We also recommend
+ setting the theme to <code>Theme.DeviceDefault.Light</code>. For example:</li>
+<pre>
+<activity android:name="com.example.MyDisplayActivity"
+ android:exported="true"
+ android:allowEmbedded="true"
+ android:taskAffinity=""
+ android:theme="@android:style/Theme.DeviceDefault.Light" />
+</pre>
+ </li>
+ <li>Create a {@link android.app.PendingIntent} for the activity that you want to display.
+ For example:
+<pre>
+Intent notificationIntent = new Intent(this, NotificationActivity.class);
+PendingIntent notificationPendingIntent = PendingIntent.getActivity(this, 0, notificationIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+</pre>
+ </li>
+ <li>Build a {@link android.app.Notification} and call
+ {@link android.app.Notification.WearableExtender#setDisplayIntent setDisplayIntent()}
+ providing the {@link android.app.PendingIntent}. The system uses this
+ {@link android.app.PendingIntent} to launch the activity when
+ users view your notification.
+ </li>
+ <li>Issue the notification using the
+ {@link android.app.NotificationManager#notify notify()} method.
+ <p class="note"><b>Note:</b> The system initially displays your notification with
+ a standard template. This template works well on all watchfaces. When users swipe the notification
+ up to view it, they'll then see your activity for the notification.</p>
+ </li>
+</ol>
+<h2 id="UiLibrary">Create Layouts with the Wearable UI Library</h2>
+<p>
+There's an unofficial UI library that is automatically included when you create your wearable
+app with the Android Studio Project Wizard. You can also add the library to your <code>build.gradle</code>
+file with the following dependency declaration:
+
+<pre>
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ <b>compile 'com.google.android.support:wearable:+'</b>
+ compile 'com.google.android.gms:play-services-wearable:+'
+}
+</pre>
+This library helps you build UIs that are designed for wearables. Here are some of the major classes:
+</p>
+
+<ul>
+ <li><code>BoxInsetLayout</code> - A FrameLayout that's aware of screen shape and can box its
+ children in the center square of a round screen.</li>
+ <li><code>CardFragment</code> - A fragment that presents content within an expandable,
+ vertically scrollable card.</li>
+ <li><code>CircledImageView</code> - An image view surrounded by a circle.</li>
+ <li><code>ConfirmationActivity</code> - An activity that displays confirmation animations after the user
+ completes an action.</li>
+ <li><code>DismissOverlayView</code> - A view for implementing long-press-to-dismiss.</li>
+ <li><code>GridViewPager</code> - A layout manager that allows the user to both vertically and
+ horizontally through pages of data. You supply an implementation of a GridPagerAdapter to
+ generate the pages that the view shows.</li>
+ <li><code>GridPagerAdapter</code> - An adapter that supplies pages to a GridViewPager.</li>
+ <li><code>FragmentGridPagerAdapter</code> - An implementation of GridPagerAdapter that
+ represents each page as a fragment.</li>
+ </li>
+ <li><code>WatchViewStub</code> - A class that can inflate a specific layout,
+ depending on the shape of the device's screen.</li>
+ <li><code>WearableListView</code> - An alternative version of ListView that is optimized for
+ ease of use on small screen wearable devices. It displays a vertically scrollable list of items,
+ and automatically snaps to the nearest item when the user stops scrolling.
+ </li>
+</ul>
+
+<p class="note"><a href="{@docRoot}shareables/training/wearable-support-docs.zip">Download the full API
+reference documentation</a> for the classes above. The documentation goes over how to use
+each UI widget.</p>
diff --git a/docs/html/training/wearables/apps/packaging.jd b/docs/html/training/wearables/apps/packaging.jd
new file mode 100644
index 0000000..ea29c9d
--- /dev/null
+++ b/docs/html/training/wearables/apps/packaging.jd
@@ -0,0 +1,156 @@
+page.title=Packaging Wearable Apps
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#Studio">Package with Android Studio</a></li>
+ <li><a href="#PackageManually">Package Manually</a></li>
+ <li><a href="#AssetCompression">Turn off Asset Compression</a></li>
+</ol>
+</div>
+</div>
+
+<p>When publishing to users, you must package a wearable app inside of a handheld app,
+because users cannot browse and install apps directly on the wearable. If packaged properly,
+when users download the handheld app, the system automatically pushes the wearable app to the
+paired wearable.
+</p>
+
+<p class="note"><b>Note:</b> This feature doesn't work when you are signing your apps with
+a debug key when developing. While developing, installing apps with <code>adb install</code> or
+Android Studio directly to the wearable is required.</p>
+
+
+<h2 id="Studio">Package with Android Studio</h2>
+<p>To properly package a wearable app in Android Studio:</p>
+
+<ol>
+ <li>Declare a Gradle dependency in the handheld app's <code>build.gradle</code> file
+ that points to the wearable app module:
+<pre>
+dependencies {
+ compile 'com.google.android.gms:play-services:5.0.+@aar'
+ compile files('../../prebuilt-libs/android-support-v4.jar')
+ <b>wearApp project(':wearable')</b>
+}
+</pre>
+ </li>
+ <li>Click <b>Build > Generate Signed APK...</b> and follow the on-screen instructions
+ to specify your release keystore and sign your app. Android Studio exports the signed
+ handheld app with the wearable app embedded in it automatically into your project's root folder.
+
+ <p>Alternatively, you can create a <code>signingConfig</code> rule in the wearable and handheld
+ modules' <code>build.gradle</code> file to sign them with your release key. Both apps must be
+ signed to have the automatic pushing of the wearable app work.
+
+<pre>
+android {
+ ...
+ signingConfigs {
+ release {
+ keyAlias 'myAlias'
+ keyPassword 'myPw'
+ storeFile file('path/to/release.keystore')
+ storePassword 'myPw'
+ }
+ }
+ buildTypes {
+ release {
+ ...
+ signingConfig signingConfigs.release
+ }d
+ }
+ ...
+}
+</pre>
+ <p>Build the handheld app by clicking the Gradle button on the right vertical toolbar of
+ Android Studio and running the <b>assembleRelease</b> task. The task is located under
+ <b>Project name > Handheld module name > assembleRelease</b>.
+ </p>
+
+<p class="note"><b>Note:</b>This example embeds the password in your Gradle file, which might be undesirable. See
+<a href="{@docRoot}sdk/installing/studio-build.html#configureSigning">Configure signing settings</a>
+for information about how to create an environment variable for the passwords instead.
+</p>
+</ol>
+
+
+<h3>Signing the wearable and handheld app separately</h3>
+<p>If your build process requires signing the wearable app separately from the handheld app,
+you can declare the following Gradle rule in the handheld module's <code>build.gradle</code> to
+embed the previously-signed wearable app:</p>
+
+<pre>
+dependencies {
+ ...
+ wearApp files('/path/to/wearable_app.apk')
+}
+</pre>
+
+<p>You then sign your handheld app in any manner you wish (either with the Android Studio
+<b>Build > Generate Signed APK...</b> menu item or with Gradle <code>signingConfig</code> rules as
+described in the previous section.</p>
+
+<h2 id="PackageManually">Package Manually</h2>
+<p>
+It's still possible to package the wearable app into the handheld app manually
+if you are using another IDE or another method of building.
+</p>
+
+<ol>
+ <li>Copy the signed wearable app into your handheld project's <code>assets/</code> directory,
+ referred to as <code>path/to/wearable_app.apk</code>.</li>
+ <li>Create a <code>res/xml/wearable_app_desc.xml</code> file that contains the version and
+ path information of the wearable app:
+<pre>
+<wearableApp package="com.google.android.wearable.myapp">
+ <versionCode>1</versionCode>
+ <versionName>1.0</versionName>
+ <path>path/to/wearable_app.apk</path>
+</wearableApp>
+</pre>
+<p>
+The <code>package</code>, <code>versionCode</code>, and <code>versionName</code> are the
+same as values specified in the wearable app's <code>AndroidManifest.xml</code> file.
+The path is the full path of <code>wearable_app.apk</code>, relative to the <code>assets/</code>
+directory.
+</p>
+</li>
+<li>
+Add a <code>meta-data</code> tag to your handheld app's <code><application></code> tag to
+reference the <code>wearable_app_desc.xml</code> file.
+<pre>
+ <meta-data android:name="com.google.android.wearable.myapp"
+ android:resource="@xml/wearable_app_desc"/>
+</pre>
+</li>
+<li>Build and sign the handheld app.</li>
+</ol>
+
+<h2 id="AssetCompression">Turn off Asset Compression</h2>
+<p>
+Many build tools automatically compress any files added to the <code>assets/</code>
+directory of an Android app. Because the wearable APK is already zipped, these tools re-compress the
+wearable APK and the wearable app installer can no longer read the wearable app.
+</p>
+
+<p> When this happens, the installation fails. On the handheld app, the <code>PackageUpdateService</code>
+logs the following error: "this file cannot be opened as a file descriptor; it is probably compressed."
+
+<p> To prevent this error in Android Studio, update your handheld app's <code>build.gradle</code> file
+with the following declaration:
+</p>
+
+<pre>
+android {
+ aaptOptions {
+ noCompress "apk"
+ }
+}
+</pre>
+
+<p>If you are using another build process, ensure that you don't doubly compress the wearable app.</p>
\ No newline at end of file
diff --git a/docs/html/training/wearables/apps/voice.jd b/docs/html/training/wearables/apps/voice.jd
new file mode 100644
index 0000000..3dea5d7
--- /dev/null
+++ b/docs/html/training/wearables/apps/voice.jd
@@ -0,0 +1,274 @@
+page.title=Adding Voice Capabilities
+@jd:body
+
+<div id="tb-wrapper">
+ <div id="tb">
+
+ <!-- Required platform, tools, add-ons, devices, knowledge, etc. -->
+ <h2>This lesson teaches you to</h2>
+ <ol>
+ <li><a href="#SystemProvided">Declare System-provided Voice Actions</a></li>
+ <li><a href="#AppProvided">Declare App-provided Voice Actions</a></li>
+ <li><a href="#FreeFormSpeech">Obtaining Free-form Speech Input</a></li>
+ </ol>
+ <h2>You should also read</h2>
+ <ul>
+ <li><a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a></li>
+ </ul>
+ </div>
+</div>
+
+<p>Voice actions are an important part of the wearable experience. They let users carry
+out actions hands-free and quickly. Wear provides two types of voice actions:</p>
+
+<dl>
+ <dt><b>System-provided</b></dt>
+ <dd>These voice actions are task-based and are built
+ into the Wear platform. You filter for them in the activity that you want to start when the
+ voice action is spoken. Examples include "Take a note" or "Set an alarm".</dd>
+ <dt><b>App-provided</b></dt>
+ <dd>These voice actions are app-based, and you declare them just like a launcher icon.
+ Users say "Start <Your App Name>" to use these voice actions and an activity that you specify
+ starts.</dd>
+</dl>
+
+<h2 id="SystemProvided" style="clear:right">Declare System-provided Voice Actions</h2>
+<p>
+The Android Wear platform provides several voice intents that are based on user actions such
+as "Take a note" or "Set an alarm". This allows users to say what they want to do and let
+the system figure out the best activity to start.</p>
+
+<p>When users speak the voice action, your app can filter for the intent that is fired to start
+an activity. If you want to start a service to do something in the background, show an activity as
+a visual cue and start the service in the activity. Make sure to call
+{@link android.app.Activity#finish finish()} when you want to get rid of the visual cue.
+</p>
+
+<p>For example, for the "Take a note" command, declare this intent filter to start an activity
+named <code>MyNoteActivity</code>:
+</p>
+
+<pre>
+ <activity android:name="MyNoteActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.SEND" />
+ <category android:name="com.google.android.voicesearch.SELF_NOTE" />
+ </intent-filter>
+ </activity>
+</pre>
+
+<p>Here is a list of the voice intents supported by the Wear platform:</p>
+
+<table>
+ <tr>
+ <th>Name</th>
+ <th>Example Phrases</th>
+ <th>Intent</th>
+ </tr>
+
+ <tr>
+ <td>Call a car/taxi</td>
+ <td>"OK Google, get me a taxi"<br/><br/>"OK Google, call me a car"</td>
+ <td>
+ <dl>
+ <dt>Action</dt>
+ <dd>
+ <code>com.google.android.gms.actions.RESERVE_TAXI_RESERVATION</code>
+ </dd>
+ </dl>
+ </td>
+ </tr>
+
+ <tr>
+ <td>Take a note</td>
+ <td>"OK Google, take a note"<br/><br/>"OK Google, note to self"</td>
+ <td>
+ <dl>
+ <dt>Action</dt>
+ <dd><code>android.intent.action.SEND</code></dd>
+ <dt>Category</dt>
+ <dd><code>com.google.android.voicesearch.SELF_NOTE</code></dd>
+ <dt>Extras</dt>
+ <dd><code>android.content.Intent.EXTRA_TEXT</code> - a string with note body</dd>
+ </dl>
+ </td>
+ </tr>
+
+ <tr>
+ <td>Set alarm</td>
+ <td>"OK Google, set an alarm for 8 AM"<br/><br/>"OK Google, wake me up at 6 tomorrow"</td>
+ <td>
+ <dl>
+ <dt>Action</dt>
+ <dd><code>android.intent.action.SET_ALARM</code></dd>
+ <dt>Extras</dt>
+ <dd><code>android.provider.AlarmClock.EXTRA_HOUR</code> - an integer with the hour of
+ the alarm.
+ <p><code>android.provider.AlarmClock.EXTRA_MINUTES</code> -
+ an integer with the minute of the alarm
+ <p>(these 2 extras are optional, either none or
+ both are provided)</p></dd>
+
+ </dl>
+ </td>
+ </tr>
+
+ <tr>
+ <td>Set timer</td>
+ <td>"Ok Google, set a timer for 10 minutes"</td>
+ <td>
+ <dl>
+ <dt>Action</dt>
+ <dd><code>android.provider.AlarmClock.ACTION_SET_TIMER</code></dd>
+ <dt>Extras</dt>
+ <dd><code>android.provider.AlarmClock.EXTRA_LENGTH</code> - an integer in the range of
+ 1 to 86400 (number of seconds in 24 hours) representing the length of the timer </dd>
+ </dl>
+ </td>
+ </tr>
+
+ <tr>
+ <td>Start/Stop a bike ride</td>
+ <td>"OK Google, start cycling"<br/><br/>"OK Google, start my bike ride"<br/><br/>"OK Google, stop cycling"</td>
+ <td>
+ <dl>
+ <dt>Action</dt>
+ <dd><code>vnd.google.fitness.TRACK</code></dd>
+ <dt>Mime Type</dt>
+ <dd><code>vnd.google.fitness.activity/biking</code></dd>
+ <dt>Extras</dt>
+ <dd><code>actionStatus</code> - a string with the value <code>ActiveActionStatus</code>
+ when starting and <code>CompletedActionStatus</code> when stopping.</dd>
+ </dl>
+ </td>
+ </tr>
+
+ <tr>
+ <td>Start/Stop a run</td>
+ <td>"OK Google, track my run"<br/><br/>"OK Google, start running"<br/><br/>"OK Google, stop running"</td>
+ <td>
+ <dl>
+ <dt>Action</dt>
+ <dd><code>vnd.google.fitness.TRACK</code></dd>
+ <dt>MimeType</dt>
+ <dd><code>vnd.google.fitness.activity/running</code></dd>
+ <dt>Extras</dt>
+ <dd><code>actionStatus</code> - a string with the value <code>ActiveActionStatus</code>
+ when starting and <code>CompletedActionStatus</code> when stopping</dd>
+ </dl>
+ </td>
+ </tr>
+
+
+ <tr>
+ <td>Start/Stop a workout</td>
+ <td>"OK Google, start a workout"<br/><br/>"OK Google, track my workout"<br/><br/>"OK Google, stop workout"</td>
+ <td>
+ <dl>
+ <dt>Action</dt>
+ <dd><code>vnd.google.fitness.TRACK</code></dd>
+ <dt>MimeType</dt>
+ <dd><code>vnd.google.fitness.activity/other</code></dd>
+ <dt>Extras</dt>
+ <dd><code>actionStatus</code> - a string with the value <code>ActiveActionStatus</code>
+ when starting and <code>CompletedActionStatus</code> when stopping</dd>
+ </dd>
+ </dl>
+ </td>
+ </tr>
+
+ <tr>
+ <td>Show heart rate</td>
+ <td>"OK Google, what’s my heart rate?"<br/><br/>"OK Google, what’s my bpm?"</td>
+ <td>
+ <dl>
+ <dt>Action</dt>
+ <dd><code>vnd.google.fitness.VIEW</code></dd>
+ <dt>Mime Type</dt>
+ <dd><code>vnd.google.fitness.data_type/com.google.heart_rate.bpm</code></dd>
+ </dd>
+ </dl>
+ </td>
+ </tr>
+
+ <tr>
+ <td>Show step count</td>
+ <td>"OK Google, how many steps have I taken?"<br/><br/>"OK Google, what’s my step count?"</td>
+ <td>
+ <dl>
+ <dt>Action</dt>
+ <dd><code>vnd.google.fitness.VIEW</code></dd>
+ <dt>Mime Type</dt>
+ <dd><code>vnd.google.fitness.data_type/com.google.step_count.cumulative</code></dd>
+ </dd>
+ </dl>
+ </td>
+ </tr>
+
+</table>
+
+<p>
+For documentation on registering for platform intents and accessing the extras information
+contained in them, see <a href="{@docRoot}guide/components/intents-common.html">Common intents</a>.
+</p>
+
+<h2 id="AppProvided">Declare App-provided Voice Actions</h2>
+<p>
+If none of the platform voice intents work for you, you can start your apps directly with
+a "Start MyActivityName" voice action. </p>
+
+<p>Registering for a "Start" action is the same as registering
+for a launcher icon on a handheld. Instead of requesting an app icon in a launcher,
+your app requests a voice action instead.</p>
+
+<p>To specify the text to say after "Start", specify a <code>label</code> attribute for the activtiy
+that you want to start. For example, this intent filter recognizes the
+"Start MyRunningApp" voice action and launches <code>StartRunActivity</code>.
+</p>
+
+<pre>
+<application>
+ <activity android:name="StartRunActivity" android:label="MyRunningApp">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+</application>
+</pre>
+
+<h2 id="FreeFormSpeech">Obtaining Free-form Speech Input</h2>
+<p>In addition to using voice actions to launch activities, you can also call the system's
+built-in Speech Recognizer activity to obtain speech input from users. This is useful to obtain input
+from users and then process it, such as doing a search or sending it as a message.</p>
+
+In your app, you call {@link android.app.Activity#startActivityForResult startActivityForResult()} using
+the {@link android.speech.RecognizerIntent#ACTION_RECOGNIZE_SPEECH} action. This starts the
+ and then handle the result
+in {@link android.app.Activity#onActivityResult onActivityResult()}.
+<pre>
+private static final int SPEECH_REQUEST_CODE = 0;
+
+// Create an intent that can start the Speech Recognizer activity
+private void displaySpeechRecognizer() {
+ Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
+ RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
+// Start the activity, the intent will be populated with the speech text
+ startActivityForResult(intent, SPEECH_REQUEST_CODE);
+}
+
+// This callback is invoked when the Speech Recognizer returns.
+// This is where you process the intent and extract the speech text from the intent.
+@Override
+protected void onActivityResult(int requestCode, int resultCode,
+ Intent data) {
+ if (requestCode == SPEECH_REQUEST && resultCode == RESULT_OK) {
+ List<String> results = data.getStringArrayListExtra(
+ RecognizerIntent.EXTRA_RESULTS);
+ String spokenText = results.get(0);
+ // Do something with spokenText
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+}
+</pre>
\ No newline at end of file
diff --git a/docs/html/training/wearables/data-layer/accessing.jd b/docs/html/training/wearables/data-layer/accessing.jd
new file mode 100644
index 0000000..4babd0a
--- /dev/null
+++ b/docs/html/training/wearables/data-layer/accessing.jd
@@ -0,0 +1,59 @@
+page.title=Accessing the Wearable Data Layer
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li>Set up a Google Play services client to use the Wearable Data Layer APIs</li>
+</ol>
+
+<h2>Dependencies and Prerequisites</h2>
+<ol>
+ <li><a href="{@docRoot}training/wearables/apps/environment.html">Creating
+ Wearable Apps > Setting up Your Environment</a></li>
+ <li><a href="{@docRoot}training/wearables/apps/creating.html">Creating
+ Wearable Apps > Creating a Project</a></li>
+</ol>
+</div>
+</div>
+
+<p>To call the data layer API, create an instance of
+<a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html"><code>GoogleApiClient</code></a>,
+the main entry point for any of the Google Play services APIs.
+</p>
+
+<p>
+<a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html"><code>GoogleApiClient</code></a>
+provides a builder that makes it easy to create an instance of the client.
+A minimal <a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html"><code>GoogleApiClient</code></a> looks like this:
+</p>
+
+<p class="note"><b>Note:</b> For now, this minimal client is enough to get started. However, see
+<a href="{@docRoot}google/auth/api-client.html">Accessing Google Play services APIs</a>
+for more information about creating a<a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html"><code>GoogleApiClient</code></a>,
+implementing its callbacks, and handling error cases.</p>
+
+<pre style="clear:right">
+GoogleApiClient mGoogleAppiClient = new GoogleApiClient.Builder(this)
+ .addConnectionCallbacks(new ConnectionCallbacks() {
+ @Override
+ public void onConnected(Bundle connectionHint) {
+ Log.d(TAG, "onConnected: " + connectionHint);
+ }
+ @Override
+ public void onConnectionSuspended(int cause) {
+ Log.d(TAG, "onConnectionSuspended: " + cause);
+ }
+ })
+ .addOnConnectionFailedListener(new OnConnectionFailedListener() {
+ @Override
+ public void onConnectionFailed(ConnectionResult result) {
+ Log.d(TAG, "onConnectionFailed: " + result);
+ }
+ })
+ .addApi(Wearable.API)
+ .build();
+</pre>
\ No newline at end of file
diff --git a/docs/html/training/wearables/data-layer/assets.jd b/docs/html/training/wearables/data-layer/assets.jd
new file mode 100644
index 0000000..73ebb73
--- /dev/null
+++ b/docs/html/training/wearables/data-layer/assets.jd
@@ -0,0 +1,123 @@
+page.title=Transferring Assets
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#TransferAsset">Transfer an Asset</a></li>
+ <li><a href="#ReceiveAsset">Receive an Asset</a></li>
+</ol>
+
+</div>
+</div>
+
+<p>
+To send large blobs of binary data over the Bluetooth transport, such as images, attach an
+<a href="{@docRoot}reference/com/google/android/gms/wearable/Asset.html">Asset</a> to a
+data item and the put the data item into the replicated data store.
+</p>
+
+<p>Assets automatically handle caching of data to prevent retransmission and conserve Bluetooth bandwidth.
+A common pattern is for a handheld app to download an image, shrink it to an appropriate size
+for display on the wearable, and transmit it to the wearable app as an Asset. The following examples
+demonstrates this pattern.
+</p>
+
+<p class="note"><b>Note:</b> Although the size of data items are limited to 100KB,
+assets can be as large as desired. However, transferring large assets affect the
+user experience in many cases, so test your apps to ensure that they perform well
+if you're transferring large assets.
+<p>
+
+<h2 id="TransferAsset">Transfer an Asset</h2>
+<p>Create the asset using one of the <code>create...()</code> methods in the
+<a href="{@docRoot}reference/com/google/android/gms/wearable/Asset.html"><code>Asset</code></a> class.
+Here, we convert a bitmap to a byte stream and then call
+<a href="{@docRoot}reference/com/google/android/gms/wearable/Asset.html#createFromBytes(byte[])"><code>createFromBytes()</code></a>
+to create the asset.
+</p>
+
+<pre>
+private static Asset createAssetFromBitmap(Bitmap bitmap) {
+ final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream);
+ return Asset.createFromBytes(byteStream.toByteArray());
+}
+</pre>
+
+<p>When you have an asset, attach it to a data item with the <code>putAsset()</code> method in
+
+<a href="{@docRoot}reference/com/google/android/gms/wearable/DataMap.html"><code>DataMap</code></a>
+or
+<a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataRequest.html"><code>PutDataRequest</code></a>
+and then put the data item into the data store with
+<a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.html#putDataItem(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.PutDataRequest)"><code>putDataItem()</code></a>:
+</p>
+
+<p><b>Using PutDataRequest</b></p>
+<pre>
+Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
+Asset asset = createAssetFromBitmap(bitmap);
+PutDataRequest request = PutDataRequest.create("/image");
+request.putAsset("profileImage", asset);
+Wearable.DataApi.putDataItem(mGoogleApiClient, request);
+</pre>
+
+<p><b>Using PutDataMapRequest</b></p>
+<pre>
+Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
+Asset asset = createAssetFromBitmap(bitmap);
+PutDataMapRequest dataMap = PutDataMapRequest.create("/image");
+dataMap.getDataMap().putAsset("profileImage", asset)
+PutDataRequest request = dataMap.asPutDataRequest();
+PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi
+ .putDataItem(mGoogleApiClient, request);
+</pre>
+
+<h2 id="ReceiveAsset">Receive assets</h2>
+
+<p>
+When an asset is created, you probably want to read and extract
+it on other side of the connection. Here's an example of how to implement the
+callback to detect an asset change and extract the Asset:
+</p>
+
+<pre>
+@Override
+public void onDataChanged(DataEventBuffer dataEvents) {
+ for (DataEvent event : dataEvents) {
+ if (event.getType() == DataEvent.TYPE_CHANGED &&
+ event.getDataItem().getUri().getPath().equals("/image")) {
+ BundleDataItem bundleDataItem = BundleDataItem.fromDataItem(dataItem);
+ Asset profileAsset = bundleDataItem.getData().getParcelable("profileImage");
+ Bitmap bitmap = loadBitmapFromAsset(profileAsset);
+ // Do something with the bitmap
+ }
+ }
+}
+
+public Bitmap loadBitmapFromAsset(Asset asset) {
+ if (asset == null) {
+ throw new IllegalArgumentException("Asset must be non-null");
+ }
+ ConnectionResult result =
+ mGoogleApiClient.blockingConnect(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ if (!result.isSuccess()) {
+ return null;
+ }
+ // convert asset into a file descriptor and block until it's ready
+ InputStream assetInputStream = Wearable.DataApi.getFdForAsset(
+ mGoogleApiClient, asset).await().getInputStream();
+ mGoogleApiClient.disconnect();
+
+ if (assetInputStream == null) {
+ Log.w(TAG, "Requested an unknown Asset.");
+ return null;
+ }
+ // decode the stream into a bitmap
+ return BitmapFactory.decodeStream(assetInputStream);
+}
+</pre>
diff --git a/docs/html/training/wearables/data-layer/data-items.jd b/docs/html/training/wearables/data-layer/data-items.jd
new file mode 100644
index 0000000..c39e37a
--- /dev/null
+++ b/docs/html/training/wearables/data-layer/data-items.jd
@@ -0,0 +1,124 @@
+page.title=Syncing Data Items
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#SyncData">Sync Data with a Data Map</a></li>
+ <li><a href="#ListenEvents">Listen for Data Item Events</a></li>
+</ol>
+
+</div>
+</div>
+
+<p>
+A <a href="@{docRoot}reference/com/google/android/gms/wearable/DataItem.html"><code>DataItem</code></a>
+defines the data interface that the system uses to synchronize data between handhelds
+and wearables. A <a href="@{docRoot}reference/com/google/android/gms/wearable/DataItem.html"><code>DataItem</code></a> generally
+consists of the following items:</p>
+<ul>
+ <li><b>Payload</b> - A byte array, which you can set with whatever data you wish, allowing you
+ to do your own object serialization and deserialization. The size of the payload is limited
+ to 100KB.</li>
+ <li><b>Path</b> - A unique string that must start with a forward slash (for instance,
+ <code>"/path/to/data"</code>)</li>
+</ul>
+
+<p>
+You normally don't implement <a href="@{docRoot}reference/com/google/android/gms/wearable/DataItem.html"><code>DataItem</code></a>
+directly. Instead, you:
+
+<ol>
+ <li>Create a <a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataRequest.html"><code>PutDataRequest</code></a> object,
+ specifying a string path to uniquely identify the item.
+ </li>
+ <li>Call <a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataRequest.html#setData(byte[])">setData()</a> to set
+ the payload.
+ </li>
+ <li>Call <a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.html#putDataItem(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.PutDataRequest)"><code>DataApi.putDataItem()</code></a> to request the system to create the data item.
+ </li>
+ <li>When requesting data items, the system returns objects
+ that properly implement the <a href="{@docRoot}reference/com/google/android/gms/wearable/DataItem.html"><code>DataItem</code></a> interface.
+ </li>
+</ol>
+
+<p>
+However, instead of working with raw bytes using <a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataRequest.html#setData(byte[])">setData()</a>,
+we recommend you <a href="#data-map">use a data map</a>, which exposes
+a data item in an easy-to-use {@link android.os.Bundle}-like interface.
+</p>
+
+<h2 id="SyncData">Sync Data with a Data Map</h2>
+<p>
+When possible, use the <a href="{@docRoot}reference/com/google/android/gms/wearable/DataMap.html"><code>DataMap</code></a> class,
+which lets you work with data items in the form of an Android {@link android.os.Bundle},
+so object serialization and de-serialization is done for you, and you can manipulate data with key-value pairs.
+</p>
+
+<p>To use a data map:</p>
+
+<ol>
+ <li>Create a
+<a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataMapRequest.html"><code>PutDataMapRequest</code></a>
+object, setting the path of the data item.
+<p class="note"><b>Note:</b> The path string is a unique identifier for the
+data item that allows you to access it from either side of the connection. The path must begin
+with a forward slash. If you're using hierarchical data in your
+app, you should create a path scheme that matches the structure of the data.
+</p>
+</li>
+<li>Call
+<a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataMapRequest.html#getDataMap()"><code>PutDataMapRequest.getDataMap()</code></a>
+</a> to obtain a data map that you can set values on.</li>
+ <li>Set any desired values for the data map using the <code>put...()</code> methods, such as
+ <a href="{@docRoot}reference/com/google/android/gms/wearable/DataMap.html#putString(java.lang.String, java.lang.String)"><code>putString()</code></a>.
+ </li>
+ <li>Call <a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataMapRequest.html#asPutDataRequest()"><code>PutDataMapRequest.asPutDataRequest()</code></a>
+ to obtain a <a href="{@docRoot}reference/com/google/android/gms/wearable/PutDataRequest.html"><code>PutDataRequest</code></a> object.
+ </li>
+ <li>Call <a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.html#putDataItem(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.PutDataRequest)"><code>DataApi.putDataItem()</code></a> to request the system to create the data item.
+ <p class="note"><b>Note:</b>
+ If the handset and wearable devices are disconnected,
+ the data is buffered and and synced when the connection is re-established.
+ </p>
+ </li>
+</ol>
+
+<p>The following example shows how to create a data map, set data on it, and create it:</p>
+
+<pre>
+PutDataMapRequest dataMap = PutDataMapRequest.create("/count");
+dataMap.getDataMap().putInt(COUNT_KEY, count++);
+PutDataRequest request = dataMap.asPutDataRequest();
+PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi
+ .putDataItem(mGoogleApiClient, request);
+</pre>
+
+<h2 id="ListenEvents">Listen for Data Item Events</h2>
+If one side of the data layer connection changes a data item, you probably want
+to be notified of any changes on the other side of the connection.
+You can do this by implementing a listener for data item events.
+
+<p>For example, here's what a typical callback looks like to carry out certain actions
+when data changes.</p>
+
+<pre>
+@Override
+public void onDataChanged(DataEventBuffer dataEvents) {
+ for (DataEvent event : dataEvents) {
+ if (event.getType() == DataEvent.TYPE_DELETED) {
+ Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri());
+ } else if (event.getType() == DataEvent.TYPE_CHANGED) {
+ Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri());
+ }
+ }
+}
+</pre>
+<p>
+This is just a snippet that requires more implementation details. Learn about
+how to implement a full listener service or activity in
+<a href="{@docRoot}training/wearables/data-layer/listeners.html">Listening for Data Layer Events</a>.
+</p>
\ No newline at end of file
diff --git a/docs/html/training/wearables/data-layer/events.jd b/docs/html/training/wearables/data-layer/events.jd
new file mode 100644
index 0000000..0146c4e
--- /dev/null
+++ b/docs/html/training/wearables/data-layer/events.jd
@@ -0,0 +1,312 @@
+page.title=Handling Data Layer Events
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#Wait">Wait for the Status of Data Layer Calls</a></li>
+ <li><a href="#Listen">Listen for Data Layer Events</a></li>
+</ol>
+
+</div>
+</div>
+
+<p>When you make calls with the data layer, you can receive the status
+of the call when it completes as well as listen for any changes that
+the call ends up making with listeners.
+</p>
+
+<h2 id="Wait">Wait for the Status of Data Layer Calls</h2>
+
+<p>You'll notice that calls to the data layer API sometimes return a
+<a href="{@docRoot}reference/com/google/android/gms/common/api/PendingResult.html"><code>PendingResult</code></a>,
+such as
+<a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.html#putDataItem(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.PutDataRequest)"><code>putDataItem()</code></a>.
+As soon as the <a href="{@docRoot}reference/com/google/android/gms/common/api/PendingResult.html"><code>PendingResult</code></a> is created,
+the operation is queued in the background. If you do nothing else after this, the operation
+eventually completes silently. However, you'll usually want to do something with the result
+after the operation completes, so the
+<a href="{@docRoot}reference/com/google/android/gms/common/api/PendingResult.html"><code>PendingResult</code></a>
+lets you wait for the result status, either synchronously or asynchronously.
+</p>
+
+<h3 id="async-waiting">Asynchronously waiting</h3>
+<p>If your code is running on the main UI thread, do not making blocking calls
+to the data layer API. You can run the calls asynchronously by adding a callback
+to the <a href="{@docRoot}reference/com/google/android/gms/common/api/PendingResult.html"><code>PendingResult</code></a> object,
+which fires when the operation is completed:</p>
+<pre>
+pendingResult.setResultCallback(new ResultCallback<DataItemResult>() {
+ @Override
+ public void onResult(final DataItemResult result) {
+ if(result.getStatus().isSuccess()) {
+ Log.d(TAG, "Data item set: " + result.getDataItem().getUri());
+ }
+});
+</pre>
+
+<h3 id="sync-waiting">Synchronously waiting</h3>
+<p>If your code is running on a separate handler thread in a background service (which is the case
+in a <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>),
+it's fine for the calls to block. In this case, you can call
+<a href="{@docRoot}reference/com/google/android/gms/common/api/PendingResult.html#await()"><code>await()</code></a>
+on the PendingResult object, which will block until the request has completed, and return a Result
+object:
+</p>
+
+<pre>
+DataItemResult result = pendingResult.await();
+if(result.getStatus().isSuccess()) {
+ Log.d(TAG, "Data item set: " + result.getDataItem().getUri());
+}
+</pre>
+
+
+<h2 id="Listen">Listen for Data Layer Events </h2>
+<p>Because the data layer synchronizes and sends data across the handheld and
+wearable, you normally want to listen for important events, such as when data items
+are created, messages are received, or when the wearable and handset are connected.
+</p>
+<p>To listen for data layer events, you have two options:</p>
+
+<ul>
+ <li>Create a service that extends
+ <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>.
+ </li>
+ <li>Create an activity that implements
+ <a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.DataListener.html"><code>DataApi.DataListener</code></a>.
+ </li>
+</ul>
+
+<p>With both these options, you override any of the data event callbacks that you care about
+handling in your implementation.</p>
+
+<h3 id="listener-service">With a WearableListenerService</h3>
+
+<p>
+You typically create instances of this service in both your wearable and handheld apps. If you
+don't care about data events in one of these apps, then you don't need to implement this
+service in that particular app.</p>
+
+<p>For example, you can have a handheld app that sets and gets data item objects and a wearable app
+that listens for these updates to update it's UI. The wearable never updates any of the data items,
+so the handheld app doesn't listen for any data events from the wearable app.</p>
+
+<p>You can listen for the following events with
+<a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>:</p>
+
+<ul>
+ <li><a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html#onDataChanged(com.google.android.gms.wearable.DataEventBuffer)"><code>onDataChanged()</code></a>
+- Called when data item objects are created, changed, or deleted. An event on one side of a connection
+triggers an this callback on both sides.</li>
+ <li><a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html#onMessageReceived(com.google.android.gms.wearable.MessageEvent)"><code>onMessageReceived()</code></a>
+- A message sent from one side of a connection triggers this callback on the other side of the connection.</li>
+ <li><a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html#onMessageReceived(com.google.android.gms.wearable.MessageEvent)"><code>onPeerConnected()</code></a>
+ and <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html#onPeerDisconnected(com.google.android.gms.wearable.Node)"><code>onPeerDisconnected()</code></a> -
+ Called when connection with the handheld or wearable is connected or disconnected.
+ Changes in connection state on one side of the connection triggers these callbacks on both sides of the connection.
+ </li>
+</ul>
+
+<p>To create a <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>:</p>
+
+<ol>
+ <li>Create a class that extends
+ <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>.
+ </li>
+ <li>Listen for the events that you care about, such as
+ <a href="{@docRoot}/reference/com/google/android/gms/wearable/WearableListenerService.html#onDataChanged(com.google.android.gms.wearable.DataEventBuffer)"><code>onDataChanged()</code></a>.
+ </li>
+ <li>Declare an intent filter in your Android manifest to notify the system about your
+ <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>.
+ This allows the system to bind your service as needed.
+ </li>
+</ol>
+
+ <p>The following example shows how to implement a simple
+ <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>:
+ </p>
+
+<pre>
+public class DataLayerListenerService extends WearableListenerService {
+
+ private static final String TAG = "DataLayerSample";
+ private static final String START_ACTIVITY_PATH = "/start-activity";
+ private static final String DATA_ITEM_RECEIVED_PATH = "/data-item-received";
+
+ @Override
+ public void onDataChanged(DataEventBuffer dataEvents) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onDataChanged: " + dataEvents);
+ }
+ final List<DataEvent> events = FreezableUtils
+ .freezeIterable(dataEvents);
+
+ GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
+ .addApi(Wearable.API)
+ .build();
+
+ ConnectionResult connectionResult =
+ googleApiClient.blockingConnect(30, TimeUnit.SECONDS);
+
+ if (!connectionResult.isSuccess()) {
+ Log.e(TAG, "Failed to connect to GoogleApiClient.");
+ return;
+ }
+
+ // Loop through the events and send a message
+ / to the node that created the data item.
+ for (DataEvent event : events) {
+ Uri uri = event.getDataItem().getUri();
+
+ // Get the node id from the host value of the URI
+ String nodeId = uri.getHost();
+ // Set the data of the message to be the bytes of the URI.
+ byte[] payload = uri.toString().getBytes();
+
+ // Send the RPC
+ Wearable.MessageApi.sendMessage(googleApiClient, nodeId,
+ DATA_ITEM_RECEIVED_PATH, payload);
+ }
+ }
+}
+</pre>
+
+<p>Here's the corresponding intent filter in the Android manifest file:</p>
+
+<pre>
+<service android:name=".DataLayerListenerService">
+ <intent-filter>
+ <action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
+ </intent-filter>
+</service>
+</pre>
+
+
+<h4>Permissions within Data Layer Callbacks</h4>
+
+<p>In order to deliver callbacks to your application for data layer events, Google Play services
+binds to your <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>,
+and calls your callbacks via IPC. This has the consequence
+that your callbacks inherit the permissions of the calling process.</p>
+
+<p>If you try to perform a privileged operation within a callback, the security check fails because your callback is
+running with the identity of the calling process, instead of the identity of your app's
+process.</p>
+
+<p>To fix this, call {@link android.os.Binder#clearCallingIdentity} </a>,
+to reset identity after crossing the IPC boundary, and then restore identity with
+{@link android.os.Binder#restoreCallingIdentity restoreCallingIdentity()} when
+you've completed the privileged operation:
+</p>
+
+<pre>
+long token = Binder.clearCallingIdentity();
+try {
+ performOperationRequiringPermissions();
+} finally {
+ Binder.restoreCallingIdentity(token);
+}
+</pre>
+
+<h3 id="Listen">With a Listener Activity</h3>
+
+<p>
+If your app only cares about data layer events when the user is interacting
+with the app and does not need a long-running service to handle every data
+change, you can listen for events in an activity by implementing one or more
+of the following interfaces:
+
+<ul>
+ <li><a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.DataListener.html"><code>DataApi.DataListener</code></a></li>
+ <li><a href="{@docRoot}reference/com/google/android/gms/wearable/MessageApi.MessageListener.html"><code>MessageApi.MessageListener</code></a></li>
+ <li><a href="{@docRoot}reference/com/google/android/gms/wearable/NodeApi.NodeListener.html"><code>NodeApi.NodeListener</code></a></li>
+</ul>
+</p>
+
+<p>To create an activity that listens for data events:</p>
+<ol>
+<li>Implement the desired interfaces.</li>
+<li>In {@link android.app.Activity#onCreate}, create an instance of
+<a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html"><code>GoogleApiClient</code></a>
+to work with the data layer API.
+<li>
+In {@link android.app.Activity#onStart onStart()}, call <a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.html#connect()"><code>connect()</code></a> to connect the client to Google Play services.
+</li>
+<li>When the connection to Google Play services is established, the system calls
+<a href="{@docRoot}reference/com/google/android/gms/common/api/GoogleApiClient.ConnectionCallbacks.html#onConnected(android.os.Bundle)"><code>onConnected()</code></a>. This is where you call
+<a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.html#addListener(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.DataApi.DataListener)"><code>DataApi.addListener()</code></a>,
+ <a href="{@docRoot}reference/com/google/android/gms/wearable/MessageApi.html#addListener(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.MessageApi.MessageListener)"><code>MessageApi.addListener()</code></a>,
+ or <a href="{@docRoot}reference/com/google/android/gms/wearable/NodeApi.html#addListener(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.NodeApi.NodeListener)"><code>NodeApi.addListener()</code></a>
+ to notify Google Play services that your activity is interested in listening for data layer events.
+</li>
+<li>In {@link android.app.Activity#onStop onStop()}, unregister any listeners with
+<a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.html#removeListener(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.DataApi.DataListener)"><code>DataApi.removeListener()</code></a>,
+<a href="{@docRoot}reference/com/google/android/gms/wearable/MessageApi.html#removeListener(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.MessageApi.MessageListener)"><code>MessageApi.removeListener()</code></a>,
+or <a href="{@docRoot}reference/com/google/android/gms/wearable/NodeApi.html#removeListener(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.wearable.NodeApi.NodeListener)"><code>NodeApi.removeListener()</code></a>.
+</li>
+<li>Implement <a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.DataListener.html#onDataChanged(com.google.android.gms.wearable.DataEventBuffer)"><code>onDataChanged()</code>,
+ <a href="{@docRoot}reference/com/google/android/gms/wearable/NodeApi.NodeListener.html#onPeerConnected(com.google.android.gms.wearable.Node)"><code>onMessageReceived()</code></a>,
+ <a href="{@docRoot}reference/com/google/android/gms/wearable/NodeApi.NodeListener.html#onPeerConnected(com.google.android.gms.wearable.Node)"><code>onPeerConnected()</code></a>, and
+ <a href="{@docRoot}reference/com/google/android/gms/wearable/NodeApi.NodeListener.html#onPeerDisconnected(com.google.android.gms.wearable.Node)"><code>onPeerDisconnected()</code></a>, depending on the interfaces that you implemented.
+</li>
+</ol>
+
+<p>Here's an example that implements
+<a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.DataListener.html"><code>DataApi.DataListener</code></a>:</p>
+
+<pre>
+public class MainActivity extends Activity implements
+ DataApi.DataListener, ConnectionCallbacks, OnConnectionFailedListener {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.main);
+ mGoogleApiClient = new GoogleApiClient.Builder(this)
+ .addApi(Wearable.API)
+ .addConnectionCallbacks(this)
+ .addOnConnectionFailedListener(this)
+ .build();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (!mResolvingError) {
+ mGoogleApiClient.connect();
+ }
+ }
+
+ @Override
+ public void onConnected(Bundle connectionHint) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Connected to Google Api Service");
+ }
+ Wearable.DataApi.addListener(mGoogleApiClient, this);
+ }
+
+ @Override
+ protected void onStop() {
+ if (null != mGoogleApiClient && mGoogleApiClient.isConnected()) {
+ Wearable.NodeApi.removeListener(mGoogleApiClient, this);
+ mGoogleApiClient.disconnect();
+ }
+ super.onStop();
+ }
+
+ @Override
+ public void onDataChanged(DataEventBuffer dataEvents) {
+ for (DataEvent event : dataEvents) {
+ if (event.getType() == DataEvent.TYPE_DELETED) {
+ Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri());
+ } else if (event.getType() == DataEvent.TYPE_CHANGED) {
+ Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri());
+ }
+ }
+ }
+</pre>
\ No newline at end of file
diff --git a/docs/html/training/wearables/data-layer/index.jd b/docs/html/training/wearables/data-layer/index.jd
new file mode 100644
index 0000000..3e06df1
--- /dev/null
+++ b/docs/html/training/wearables/data-layer/index.jd
@@ -0,0 +1,87 @@
+page.title=Sending and Syncing Data
+@jd:body
+
+<div id="tb-wrapper">
+ <div id="tb">
+
+ <!-- Required platform, tools, add-ons, devices, knowledge, etc. -->
+ <h2>Dependencies and prerequisites</h2>
+ <ul>
+ <li>Android 4.3 (API Level 18) or higher on the handset device</li>
+ <li>The latest version of <a href="{@docRoot}google/play">Google Play services</a></li>
+ <li>An Android Wear device or Wear AVD</li>
+ </ul>
+ </div>
+</div>
+
+<p>
+The Wearable Data Layer API, which is part of Google Play services, provides a communication channel
+for your handheld and wearable apps. The API consists of a set of data objects that the system can
+send and synchronize over the wire and listeners that notify your apps of important events with
+the data layer:</p>
+
+<dl>
+ <dt><b>Data Items</b></dt>
+ <dd>A <a href="{@docRoot}reference/com/google/android/gms/wearable/DataItem.html"><code>DataItem</code></a>
+ provides data storage with automatic syncing between the handheld and
+ wearable.</dd>
+
+ <dt><b>Messages</b></dt>
+ <dd>The <a href="{@docRoot}reference/com/google/android/gms/wearable/MessageApi.html"><code>MessageApi</code></a> class
+ can send messages designed for "fire-and-forget" commands, such as controlling a handheld's
+ media player from the wearable or starting an intent on the wearable from the handheld.
+ The system always delivers the message when the handheld and wearable are connected and delivers
+ an error when the devices are disconnected. Messages are great for one-way requests or for a
+ request/response communication model.</dd>
+
+ <dt><b>Asset</b></dt>
+ <dd><a href="{@docRoot}reference/com/google/android/gms/wearable/Asset.html"><code>Asset</code></a> objects are for
+ sending binary blobs of data, such as images. You attach assets to data items and the system
+ automatically takes care of the transfer for you, conserving Bluetooth bandwidth by caching large assets
+ to avoid re-transmission.</dd>
+
+ <dt><b>WearableListenerService</b> (for services)</dt>
+ <dd><p>Extending <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>
+ lets you listen for important data layer events in a service. The system manages the lifecycle of
+ the <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>,
+ binding to the service when it needs to send data items or messages and unbinding the service when no work is needed.</p>
+ </dd>
+
+ <dt><b>DataListener</b> (for foreground activities)</dt>
+ <dd>
+ Implementing <a href="{@docRoot}reference/com/google/android/gms/wearable/DataApi.DataListener.html"><code>DataListener</code></a>
+ in an activity lets you listen for important data layer events when an activity
+ is in the foreground. Using this instead of the
+ <a href="{@docRoot}reference/com/google/android/gms/wearable/WearableListenerService.html"><code>WearableListenerService</code></a>
+ lets you listen for changes only when the user is actively using your app.
+ </dd>
+</dl>
+
+<p class="warning"><b>Warning:</b>
+Because these APIs are designed for communication between handhelds and wearables,
+these are the only APIs you should use to set up communication between these
+devices. For instance, don't try to open low-level sockets to create a communication
+channel.
+</p>
+
+<h2>Lessons</h2>
+ <dl>
+ <dt><a href="{@docRoot}training/wearables/data-layer/data-items.html">Accessing the Wearable Data Layer</a></dt>
+ <dd>This lesson shows you how to create a client to access the Data Layer APIs.</dd>
+
+ <dt><a href="{@docRoot}training/wearables/data-layer/data-items.html">Syncing Data Items</a></dt>
+ <dd>Data items are objects that are stored in a replicated data store that is automatically
+ synced from handhelds to wearables.</dd>
+
+ <dt><a href="{@docRoot}training/wearables/data-layer/assets.html">Transferring Assets</a></dt>
+ <dd>Assets are binary blobs of data that you typically use to transfer images or media.</dd>
+
+ <dt><a href="{@docRoot}training/wearables/data-layer/stacks.html">Sending and Receiving Messages</a></dt>
+ <dd>Messages are designed for fire-and-forget messages that you can send back and forth
+ between the wearable and handheld.</dd>
+
+ <dt><a href="{@docRoot}training/wearables/data-layer/events.html">Handling Data Layer Events</a></dt>
+ <dd>Be notified of changes and events to the data layer.</dd>
+ </dl>
+
+</div>
\ No newline at end of file
diff --git a/docs/html/training/wearables/data-layer/messages.jd b/docs/html/training/wearables/data-layer/messages.jd
new file mode 100644
index 0000000..a9dec75
--- /dev/null
+++ b/docs/html/training/wearables/data-layer/messages.jd
@@ -0,0 +1,99 @@
+page.title=Sending and Receiving Messages
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#SendMessage">Send a Message</a></li>
+ <li><a href="#ReceiveMessage">Receive a Message</a></li>
+</ol>
+</div>
+</div>
+
+<p>You send messages using the
+<a href="{@docRoot}reference/com/google/android/gms/wearable/MessageApi.html"><code>MessageApi</code></a>
+and attach the following items to the message:</p>
+
+<ul>
+ <li>An arbitrary payload (optional)</li>
+ <li>A path that uniquely identifies the message's action</li>
+</ul>
+<p>
+Unlike data items, there is no syncing between the handheld and wearable apps.
+Messages are a one-way communication mechanism that's meant for
+"fire-and-forget" tasks, such as sending a message to the wearable
+to start an activity. You can also use messages in request/response model
+where one side of the connection sends a message, does some work,
+sends back a response message.</p>
+
+<h2 id="SendMessage">Send a Message</h2>
+
+<p>The following example shows how to send a message that indicates to the other
+side of the connect to start an activity.
+This call is made synchronously, which blocks until the message
+is received or when the request times out:
+</p>
+
+<p class="note"><b>Note:</b> Read more about asynchronous and synchronous calls
+to Google Play services and when to use each in
+<a href="google/auth/api-client.html#Communicating">Communicate with Google Play Services</a>.
+</p>
+
+<pre>
+Node node; // the connected device to send the message to
+GoogleApiClient mGoogleApiClient;
+public static final START_ACTIVITY_PATH = "/start/MainActivity";
+...
+
+ SendMessageResult result = Wearable.MessageApi.sendMessage(
+ mGoogleApiClient, node, START_ACTIVITY_PATH, null).await();
+ if (!result.getStatus().isSuccess()) {
+ Log.e(TAG, "ERROR: failed to send Message: " + result.getStatus());
+ }
+</pre>
+
+<p>
+Here's a simple way to get a list of connected nodes that you can potentially
+send messages to:</p>
+
+<pre>
+private Collection<String> getNodes() {
+ HashSet <String>results= new HashSet<String>();
+ NodeApi.GetConnectedNodesResult nodes =
+ Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).await();
+ for (Node node : nodes.getNodes()) {
+ results.add(node.getId());
+ }
+ return results;
+}
+</pre>
+
+<h2 id="ReceiveMessage">Receiving a Message</h2>
+
+<p>
+
+To be notified of received messages, you implement a listener for message events.
+This example shows how you might do this by checking the <code>START_ACTIVITY_PATH</code>
+that the previous example used to send the message. If this condition is <code>true</code>,
+a specific activity is started.
+</p>
+
+<pre>
+@Override
+public void onMessageReceived(MessageEvent messageEvent) {
+ if (messageEvent.getPath().equals(START_ACTIVITY_PATH)) {
+ Intent startIntent = new Intent(this, MainActivity.class);
+ startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(startIntent);
+ }
+}
+</pre>
+
+<p>
+This is just a snippet that requires more implementation details. Learn about
+how to implement a full listener service or activity in
+<a href="#listening">Listening for Data Layer Events</a>.
+</p>
\ No newline at end of file
diff --git a/docs/html/training/wearables/notifications/creating.jd b/docs/html/training/wearables/notifications/creating.jd
new file mode 100644
index 0000000..a61fa07
--- /dev/null
+++ b/docs/html/training/wearables/notifications/creating.jd
@@ -0,0 +1,295 @@
+page.title=Creating a Notification
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#Import">Import the Necessary Classes</a></li>
+ <li><a href="#NotificationBuilder">Create Notifications with the Notification Builder</a></li>
+ <li><a href="#ActionButtons">Add Action Buttons</a></li>
+ <li><a href="#SpecifyWearableOnlyActions">Specify Wearable-only Actions</a></li>
+ <li><a href="#BigView">Add a Big View</a></li>
+ <li><a href="#AddWearableFeatures">Add Wearable Features for a Notification</a></li>
+ <li><a href="#Deliver">Deliver the Notification</a></li>
+</ol>
+
+</div>
+</div>
+
+<p>To build handheld notifications that are also sent to wearables, use
+{@link android.support.v4.app.NotificationCompat.Builder}. When you build
+notifications with this class, the system takes care of displaying
+notifications properly, whether they appear on a handheld or wearable.
+</p>
+
+<p class="note"><strong>Note:</strong>
+Notifications using {@link android.widget.RemoteViews} are stripped of custom
+layouts and the wearable only displays the text and icons. However, you can create
+<a href="{@docRoot}training/wearables/apps/layouts.html#CustomNotifications">create custom notifications</a>
+that use custom card layouts by creating a wearable app that runs on the wearable device.</p>
+</div>
+
+<h2 id="Import">Import the necessary classes</h2>
+<p>Before you begin, import the necessary classes from the support library:</p>
+
+<pre style="clear:right">
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.support.v4.app.NotificationCompat.WearableExtender;
+</pre>
+
+<h2 id="NotificationBuilder">Create Notifications with the Notification Builder</h2>
+
+<p>The <a href="http://developer.android.com/tools/support-library/features.html#v4">v4
+support library</a> allows you to create notifications using the latest notification features
+such as action buttons and large icons, while remaining compatible with Android 1.6 (API level
+4) and higher.</p>
+
+<p>To create a notification with the support library, you create an instance of
+{@link android.support.v4.app.NotificationCompat.Builder} and issue the notification by
+passing it to {@link android.support.v4.app.NotificationManagerCompat#notify notify()}. For example:
+</p>
+
+<pre>
+int notificationId = 001;
+// Build intent for notification content
+Intent viewIntent = new Intent(this, ViewEventActivity.class);
+viewIntent.putExtra(EXTRA_EVENT_ID, eventId);
+PendingIntent viewPendingIntent =
+ PendingIntent.getActivity(this, 0, viewIntent, 0);
+
+NotificationCompat.Builder notificationBuilder =
+ new NotificationCompat.Builder(this)
+ .setSmallIcon(R.drawable.ic_event)
+ .setContentTitle(eventTitle)
+ .setContentText(eventLocation)
+ .setContentIntent(viewPendingIntent);
+
+// Get an instance of the NotificationManager service
+NotificationManagerCompat notificationManager =
+ NotificationManagerCompat.from(this);
+
+// Build the notification and issues it with notification manager.
+notificationManager.notify(notificationId, notificationBuilder.build());
+</pre>
+
+<p>When this notification appears on a handheld device, the user can invoke the
+{@link android.app.PendingIntent}
+specified by the {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent
+setContentIntent()} method by touching the notification. When this
+notification appears on an Android wearable, the user can swipe the notification to the left to
+reveal the <strong>Open</strong> action, which invokes the intent on the handheld device.</p>
+
+
+<img src="{@docRoot}wear/images/circle_email_action.png" height="200"
+ style="float:right;clear:right;margin:0 0 20px 60px" />
+
+<h2 id="ActionButtons">Add Action Buttons</h2>
+
+<p>In addition to the primary content action defined by
+{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent
+setContentIntent()}, you can add other actions by passing a {@link android.app.PendingIntent} to
+the {@link android.support.v4.app.NotificationCompat.Builder#addAction addAction()} method.</p>
+
+<p>For example, the following code shows the same type of notification from above, but adds an
+action to view the event location on a map.</p>
+
+<pre style="clear:right">
+// Build an intent for an action to view a map
+Intent mapIntent = new Intent(Intent.ACTION_VIEW);
+Uri geoUri = Uri.parse("geo:0,0?q=" + Uri.encode(location));
+mapIntent.setData(geoUri);
+PendingIntent mapPendingIntent =
+ PendingIntent.getActivity(this, 0, mapIntent, 0);
+
+NotificationCompat.Builder notificationBuilder =
+ new NotificationCompat.Builder(this)
+ .setSmallIcon(R.drawable.ic_event)
+ .setContentTitle(eventTitle)
+ .setContentText(eventLocation)
+ .setContentIntent(viewPendingIntent)
+ <b>.addAction(R.drawable.ic_map,
+ getString(R.string.map), mapPendingIntent);</b>
+</pre>
+
+<p>On a handheld, the action appears as an
+additional button attached to the notification. On a wearable, the action appears as
+a large button when the user swipes the notification to the left. When the user taps the action,
+the associated intent is invoked on the handheld.</p>
+
+<p class="note"><strong>Tip:</strong> If your notifications include a "Reply" action
+ (such as for a messaging app), you can enhance the behavior by enabling
+ voice input replies directly from the Android wearable. For more information, read
+ <a href="{@docRoot}training/wearables/notifications/voice-input.html">Receiving Voice Input from
+ a Notification</a>.
+</p>
+
+<h2 id="SpecifyWearableOnlyActions">Specify Wearable-only Actions</h2>
+
+<p>
+If you want the actions available on the wearable to be different from those on the handheld,
+then use {@link android.support.v4.app.NotificationCompat.WearableExtender#addAction WearableExtender.addAction()}.
+Once you add an action with this method, the wearable does not display any other actions added with
+{@link android.support.v4.app.NotificationCompat.Builder#addAction NotificationCompat.Builder.addAction()}.
+That is, only the actions added with {@link android.support.v4.app.NotificationCompat.WearableExtender#addAction WearableExtender.addAction()} appear on the wearable and they do not appear on the handheld.
+</p>
+
+<pre>
+// Create an intent for the reply action
+Intent actionIntent = new Intent(this, ActionActivity.class);
+PendingIntent actionPendingIntent =
+ PendingIntent.getActivity(this, 0, actionIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+// Create the action
+NotificationCompat.Action action =
+ new NotificationCompat.Action.Builder(R.drawable.ic_action,
+ getString(R.string.label, actionPendingIntent))
+ .build();
+
+// Build the notification and add the action via WearableExtender
+Notification notification =
+ new NotificationCompat.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_message)
+ .setContentTitle(getString(R.string.title))
+ .setContentText(getString(R.string.content))
+ .extend(new WearableExtender().addAction(action))
+ .build();
+</pre>
+<h2 id="BigView">Add a Big View</h2>
+
+<img src="{@docRoot}wear/images/06_images.png" height="200"
+ style="float:right;margin:0 0 20px 40px" />
+
+<p>You can insert extended text content
+to your notification by adding one of the "big view" styles to your notification. On a
+handheld device, users can see the big view content by expanding the notification. On
+a wearable device, the big view content is visible by default.</p>
+
+<p>To add the extended content to your notification, call {@link
+android.support.v4.app.NotificationCompat.Builder#setStyle setStyle()} on the {@link
+android.support.v4.app.NotificationCompat.Builder} object, passing it an instance of either
+{@link android.support.v4.app.NotificationCompat.BigTextStyle BigTextStyle} or
+{@link android.support.v4.app.NotificationCompat.InboxStyle InboxStyle}.</p>
+
+<p>For example, the following code adds an instance of
+{@link android.support.v4.app.NotificationCompat.BigTextStyle} to the event notification,
+in order to include the complete event description (which includes more text than can fit
+into the space provided for {@link android.support.v4.app.NotificationCompat.Builder#setContentText
+setContentText()}).</p>
+
+<pre style="clear:right">
+// Specify the 'big view' content to display the long
+// event description that may not fit the normal content text.
+BigTextStyle bigStyle = new NotificationCompat.BigTextStyle();
+bigStyle.bigText(eventDescription);
+
+NotificationCompat.Builder notificationBuilder =
+ new NotificationCompat.Builder(this)
+ .setSmallIcon(R.drawable.ic_event)
+ .setLargeIcon(BitmapFractory.decodeResource(
+ getResources(), R.drawable.notif_background))
+ .setContentTitle(eventTitle)
+ .setContentText(eventLocation)
+ .setContentIntent(viewPendingIntent)
+ .addAction(R.drawable.ic_map,
+ getString(R.string.map), mapPendingIntent)
+ <b>.setStyle(bigStyle);</b>
+</pre>
+
+<p>Notice that you can add a large background image to any notification using the
+{@link android.support.v4.app.NotificationCompat.Builder#setLargeIcon setLargeIcon()}
+method. For more information about designing notifications with large images, see the
+<a href="{@docRoot}design/wear/index.html">Design Principles of Android
+Wear</a>.</p>
+
+<h2 id="AddWearableFeatures">Add Wearable Features For a Notification</h2>
+
+<p>If you ever need to add wearable-specific options to a notification, such as specifying additional
+pages of content or letting users dictate a text response with voice input, you can use the
+{@link android.support.v4.app.NotificationCompat.WearableExtender} class to
+specify the options. To use this API:</p>
+
+<ol>
+ <li>Create an instance of a {@link android.support.v4.app.NotificationCompat.WearableExtender WearableExtender},
+ setting the wearable-specific options for the notication.</li>
+ <li>Create an instance of
+ {@link android.support.v4.app.NotificationCompat.Builder}, setting the
+ desired properties for your notification as described earlier in this lesson.</li>
+ <li>Call {@link android.support.v4.app.NotificationCompat.Builder#extend extend()} on
+ the notification and pass in the
+ {@link android.support.v4.app.NotificationCompat.WearableExtender WearableExtender}. This applies
+ the wearable options to the notification.</li>
+ <li>Call {@link android.support.v4.app.NotificationCompat.Builder#build} to build the notification.</li>
+</ol>
+
+<p>
+For example, the following code calls the
+{@link android.support.v4.app.NotificationCompat.WearableExtender#setHintHideIcon setHintHideIcon()}
+method to remove the app icon from the notification card.
+</p>
+
+<pre>
+// Create a WearableExtender to add functionality for wearables
+NotificationCompat.WearableExtender wearableExtender =
+ new NotificationCompat.WearableExtender()
+ .setHintHideIcon(true);
+
+// Create a NotificationCompat.Builder to build a standard notification
+// then extend it with the WearableExtender
+Notification notif = new NotificationCompat.Builder(mContext)
+ .setContentTitle("New mail from " + sender)
+ .setContentText(subject)
+ .setSmallIcon(R.drawable.new_mail);
+ .extend(wearableExtender)
+ .build();
+</pre>
+
+<p>The
+ {@link android.support.v4.app.NotificationCompat.WearableExtender#setHintHideIcon setHintHideIcon()}
+ method is just one example of new notification features available with
+ {@link android.support.v4.app.NotificationCompat.WearableExtender}.
+</p>
+
+<p>If you ever need to read wearable-specifc options at a later time, use the corresponding get
+method for the option. This example calls the
+{@link android.support.v4.app.NotificationCompat.WearableExtender#getHintHideIcon()} method to
+get whether or not this notification hides the icon:
+<pre>
+NotificationCompat.WearableExtender wearableExtender =
+ new NotificationCompat.WearableExtender(notif);
+boolean hintHideIcon = wearableExtender.getHintHideIcon();
+</pre>
+
+<h2 id="Deliver">Deliver the Notification</h2>
+<p>When you want to deliver your notifications, always use the
+ {@link android.support.v4.app.NotificationManagerCompat} API instead of
+ {@link android.app.NotificationManager}:</p>
+
+<pre>
+// Get an instance of the NotificationManager service
+NotificationManagerCompat notificationManager =
+ NotificationManagerCompat.from(mContext);
+
+// Issue the notification with notification manager.
+notificationManager.notify(notificationId, notif);
+</pre>
+
+<p>If you use the framework's {@link android.app.NotificationManager}, some
+features from {@link android.support.v4.app.NotificationCompat.WearableExtender}
+do not work, so make sure to use {@link android.support.v4.app.NotificationCompat}.
+</p>
+
+<pre>
+NotificationCompat.WearableExtender wearableExtender =
+ new NotificationCompat.WearableExtender(notif);
+boolean hintHideIcon = wearableExtender.getHintHideIcon();
+ </pre>
+
+<p>The {@link android.support.v4.app.NotificationCompat.WearableExtender} APIs allow you to add
+additional pages to notifications, stack notifications, and more. Continue to the following lessons
+to learn about these features.
+</p>
\ No newline at end of file
diff --git a/docs/html/training/wearables/notifications/index.jd b/docs/html/training/wearables/notifications/index.jd
new file mode 100644
index 0000000..17f3cb3
--- /dev/null
+++ b/docs/html/training/wearables/notifications/index.jd
@@ -0,0 +1,51 @@
+page.title=Adding Wearable Features to Notifications
+@jd:body
+
+<div id="tb-wrapper">
+ <div id="tb">
+
+ <h2>Dependencies and prerequisites</h2>
+ <ul>
+ <li>Android 4.3 (API Level 18) or higher on the handset device</li>
+ <li>An Android Wear device or Wear AVD</li>
+ <li><a href="{@docRoot}tools/support-library/features.html#v4">The Android v4 support library
+ (or v13, which includes v4)</li>
+ </ul>
+ <h2>You should also read</h2>
+ <ul>
+ <li><a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a></li>
+ </ul>
+ </div>
+</div>
+
+<p>When an Android handheld (phone or tablet) and Android wearable are connected, the handheld
+automatically shares notifications with the wearable. On the wearable, each
+notification appears as a new card in the <a href="{@docRoot}design/wear/index.html"
+>context stream</a>.</p>
+
+<p>However, to give users the best experience, you should add wearable-specific functionality to the
+notifications you already create. The following lessons show you how to
+create notifications that are catered for handhelds and wearables at the same time.
+</p>
+
+<img src="{@docRoot}wear/images/notification_phone@2x.png" width="700" height="265" />
+
+<p class="caption"><b>Figure 1.</b> The same notification displayed on a handheld and wearable.</p>
+
+<h2>Lessons</h2>
+ <dl>
+ <dt><a href="{@docRoot}training/wearables/notifications/creating.html">Creating a notification</a></dt>
+ <dd>Learn how to create notifications with wearable features with the Android support library.</dd>
+ <dt><a href="{@docRoot}training/wearables/notifications/voice-input.html">Receiving Voice Input
+in a Notification</a></dt>
+ <dd>Learn how to add an action to a wearable notification that receives voice input from users and delivers
+ the transcribed message to your handset app.</dd>
+ <dt><a href="{@docRoot}training/wearables/notifications/pages.html">Adding Pages to a Notification</a></dt>
+ <dd>Learn how to add additional pages of information that are visible when the user
+swipes to the left.</dd>
+ <dt><a href="{@docRoot}training/wearables/notifications/stacks.html">Stacking Notifications</a></dt>
+ <dd>Learn how to place all similar notifications from your app in a stack, allowing users to view
+ each notification individually without adding multiple cards to the card stream.</dd>
+ </dl>
+
+</div>
\ No newline at end of file
diff --git a/docs/html/training/wearables/notifications/pages.jd b/docs/html/training/wearables/notifications/pages.jd
new file mode 100644
index 0000000..d74c8ea
--- /dev/null
+++ b/docs/html/training/wearables/notifications/pages.jd
@@ -0,0 +1,71 @@
+page.title=Adding Pages to a Notification
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li>Add Pages to a Notification</li>
+</ol>
+</div>
+</div>
+
+<p>When you'd like to provide more information without requiring users
+to open your app on their handheld device, you can
+add one or more pages to the notification on the wearable. The additional pages
+appear immediately to the right of the main notification card.
+</p>
+
+<img src="{@docRoot}wear/images/09_pages.png" height="200" style="margin:0 0 20px 0" />
+<img src="{@docRoot}wear/images/08_pages.png" height="200" style="margin:0 0 20px 40px" />
+
+<p>To create a notification with multiple pages:</p>
+<ol>
+ <li>Create the main notification (the first page) with
+ {@link android.support.v4.app.NotificationCompat.Builder},
+ in the way you'd like the notification to appear on a handset.</li>
+ <li>Create the additional pages for the wearable with
+ {@link android.support.v4.app.NotificationCompat.Builder}.</li>
+ <li>Apply the pages to the main notification with the
+ {@link android.support.v4.app.NotificationCompat.WearableExtender#addPage addPage()}
+ method or add multiple pages in a {@link java.util.Collection} with the
+ {@link android.support.v4.app.NotificationCompat.WearableExtender#addPage addPages()} method.
+ </li>
+</ol>
+
+<p>For example, here's some code that adds a second page to a notification:</p>
+
+<pre>
+// Create builder for the main notification
+NotificationCompat.Builder notificationBuilder =
+ new NotificationCompat.Builder(this)
+ .setSmallIcon(R.drawable.new_message)
+ .setContentTitle("Page 1")
+ .setContentText("Short message")
+ .setContentIntent(viewPendingIntent);
+
+// Create a big text style for the second page
+BigTextStyle secondPageStyle = new NotificationCompat.BigTextStyle();
+secondPageStyle.setBigContentTitle("Page 2")
+ .bigText("A lot of text...");
+
+// Create second page notification
+Notification secondPageNotification =
+ new NotificationCompat.Builder(this)
+ .setStyle(secondPageStyle)
+ .build();
+
+// Add second page with wearable extender and extend the main notification
+Notification twoPageNotification =
+ new WearableExtender()
+ .addPage(secondPageNotification)
+ .extend(notificationBuilder)
+ .build();
+
+// Issue the notification
+notificationManager =
+ NotificationManagerCompat.from(this);
+notificationManager.notify(notificationId, twoPageNotification);
+</pre>
\ No newline at end of file
diff --git a/docs/html/training/wearables/notifications/stacks.jd b/docs/html/training/wearables/notifications/stacks.jd
new file mode 100644
index 0000000..e71e74c
--- /dev/null
+++ b/docs/html/training/wearables/notifications/stacks.jd
@@ -0,0 +1,154 @@
+page.title=Stacking Notifications
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#AddGroup">Add Each Notification to a Group</a></li>
+ <li><a href="#AddSummary">Add a Summary Notification</a></li>
+</ol>
+
+</div>
+</div>
+<img src="/wear/images/11_bundles_B.png" height="200" width="169"
+ style="margin:0 0 20px 20px; clear:both; float:right" alt="">
+<img src="/wear/images/11_bundles_A.png" height="200" width="169"
+ style="margin:0 20px 20px 20px; float:right" alt="">
+<p>When creating notifications for a handheld device, you should always aggregate similar
+notifications into a single summary notification. For example, if your app creates notifications
+for received messages, you should not show more than one notification
+on a handheld device—when more than one is message is received, use a single notification
+to provide a summary such as "2 new messages."</p>
+
+<p>However, a summary notification is less useful on an Android wearable because users
+are not able to read details from each message on the wearable (they must open your app on the
+handheld to view more information). So for the wearable device, you should
+group all the notifications together in a stack. The stack of notifications appears as a single
+card, which users can expand to view the details from each notification separately. The new
+{@link android.support.v4.app.NotificationCompat.Builder#setGroup setGroup()} method makes this
+possible while allowing you to still provide only one summary notification on the handheld device.</p>
+
+<h2 id="AddGroup" style="clear:right">Add Each Notification to a Group</h2>
+
+<p>To create a stack, call {@link android.support.v4.app.NotificationCompat.Builder#setGroup setGroup()}
+for each notification you want in the stack and specify a
+group key. Then call {@link android.support.v4.app.NotificationManagerCompat#notify notify()}
+to send it to the wearable.</p>
+
+<pre style="clear:right">
+final static String GROUP_KEY_EMAILS = "group_key_emails";
+
+// Build the notification, setting the group appropriately
+Notification notif = new NotificationCompat.Builder(mContext)
+ .setContentTitle("New mail from " + sender1)
+ .setContentText(subject1)
+ .setSmallIcon(R.drawable.new_mail);
+ .setGroup(GROUP_KEY_EMAILS)
+ .build();
+
+// Issue the notification
+NotificationManagerCompat notificationManager =
+ NotificationManagerCompat.from(this);
+notificationManager.notify(notificationId1, notif);
+</pre>
+
+<p>Later on, when you create another notification, specify
+the same group key. When you call
+{@link android.support.v4.app.NotificationManagerCompat#notify notify()},
+this notification appears in the same stack as the previous notification,
+instead of as a new card:</p>
+
+<pre style="clear:right">
+Notification notif2 = new NotificationCompat.Builder(mContext)
+ .setContentTitle("New mail from " + sender2)
+ .setContentText(subject2)
+ .setSmallIcon(R.drawable.new_mail);
+ .setGroup(GROUP_KEY_EMAILS)
+ .build();
+
+notificationManager.notify(notificationId2, notif2);
+</pre>
+
+<p>By default, notifications appear in the order in which you added them, with the most recent
+ notification visible at the top. You can order notifications in another fashion by calling
+ {@link android.support.v4.app.NotificationCompat.Builder#setSortKey setSortKey()}.</p>
+
+
+<h2 id="AddSummary">Add a Summary Notification</h2>
+
+<img src="{@docRoot}wear/images/notif_summary_framed.png" height="242" width="330" style="float:right;margin:0 0 20px 40px" alt="" />
+
+<p>It's important that you still provide a summary notification that appears on handheld devices.
+So in addition to adding each unique notification to the same stack group, also add a summary
+notification and call {@link android.support.v4.app.NotificationCompat.Builder#setGroupSummary setGroupSummary()}
+on the summary notification.</p>
+
+<p>This notification does not appear in your stack of notifications on the wearable, but
+appears as the only notification on the handheld device.</p>
+
+<pre style="clear:right">
+Bitmap largeIcon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_large_icon);
+
+// Create an InboxStyle notification
+Notification summaryNotification = new NotificationCompat.Builder(mContext)
+ .setContentTitle("2 new messages")
+ .setSmallIcon(R.drawable.ic_small_icon)
+ .setLargeIcon(largeIcon)
+ .setStyle(new NotificationCompat.InboxStyle()
+ .addLine("Alex Faaborg Check this out")
+ .addLine("Jeff Chang Launch Party")
+ .setBigContentTitle("2 new messages")
+ .setSummaryText("johndoe@gmail.com"))
+ .setGroup(GROUP_KEY_EMAILS)
+ .setGroupSummary(true)
+ .build();
+
+notificationManager.notify(notificationId3, summaryNotification);
+</pre>
+
+<p>
+This notification uses {@link android.support.v4.app.NotificationCompat.InboxStyle},
+which gives you an easy way to create notifications for email or messaging apps.
+You can use this style, another one defined in {@link android.support.v4.app.NotificationCompat},
+or no style for the summary notification.
+</p>
+
+<p class="note"><b>Tip:</b>
+To style the text like in the example screenshot, see
+<a href="{@docRoot}guide/topics/resources/string-resource.html#StylingWithHTML">Styling
+with HTML markup</a> and
+<a href="{@docRoot}guide/topics/resources/string-resource.html#StylingWithSpannables">Styling
+with Spannables</a>.
+</p>
+
+<p>Summary notifications can also affect notifications on wearables without being displayed on them.
+When creating a summary notification, you can use the
+{@link android.support.v4.app.NotificationCompat.WearableExtender} class and call
+{@link android.support.v4.app.NotificationCompat.WearableExtender#setBackground setBackground()} or
+{@link android.support.v4.app.NotificationCompat.WearableExtender#addAction addAction()} to set
+a background image or an action that applies to the entire stack on the wearable. For instance,
+to set the background for an entire stack of notifications:
+</p>
+
+<pre>
+Bitmap background = BitmapFactory.decodeResource(getResources(),
+ R.drawable.ic_background);
+
+NotificationCompat.WearableExtender wearableExtender =
+ new NotificationCompat.WearableExtender()
+ .setBackground(background);
+
+// Create an InboxStyle notification
+Notification summaryNotificationWithBackground =
+ new NotificationCompat.Builder(mContext)
+ .setContentTitle("2 new messages")
+ ...
+ .extend(wearableExtender)
+ .setGroup(GROUP_KEY_EMAILS)
+ .setGroupSummary(true)
+ .build();
+</pre>
\ No newline at end of file
diff --git a/docs/html/training/wearables/notifications/voice-input.jd b/docs/html/training/wearables/notifications/voice-input.jd
new file mode 100644
index 0000000..4a27826
--- /dev/null
+++ b/docs/html/training/wearables/notifications/voice-input.jd
@@ -0,0 +1,175 @@
+page.title=Receiving Voice Input in a Notification
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#VoiceInput">Define the Voice Input</a></li>
+ <li><a href="#AddAction">Add the Voice Input as a Notification Action</li>
+ <li><a href="#ReceiveInput">Receiving the Voice Input as a String</a>
+</ol>
+
+</div>
+</div>
+
+<p>If you have handheld notifications that include an action to input text,
+such as reply to an email, it should normally launch an activity
+on the handheld device to input the text. However, when your notification appears on a wearable,
+there is no keyboard input, so you can let users dictate a reply or provide pre-defined text messages
+using {@link android.support.v4.app.RemoteInput}.
+</p>
+
+<p>When users reply with voice or select one of the available
+messages, the system attaches the text response to the {@link android.content.Intent} you specified
+for the notification action and sends that intent to your handheld app.</p>
+
+<img src="{@docRoot}wear/images/13_voicereply.png" height="200" width="169"
+style="float:right;margin:0 0 20px 40px;clear:right" />
+<img src="{@docRoot}wear/images/03_actions.png" height="200" width="169"
+style="float:right;margin:0 0 20px 40px" />
+
+<p class="note"><strong>Note:</strong> The Android emulator does not support voice input. When
+using the emulator for a wearable device, enable <b>Hardware keyboard present</b> in the AVD settings
+so you can type replies instead.</p>
+
+<h2 id="VoiceInput">Define the Voice Input</h2>
+
+<p>To create an action that supports voice input, create an instance of
+ {@link android.support.v4.app.RemoteInput.Builder} that you can add to your notification action.
+ This class's constructor accepts a string that the system uses as
+ the key for the voice input, which you'll later use to retrieve the text of the
+ input in your handheld app.</p>
+
+<p>For example, here's how to create a
+{@link android.support.v4.app.RemoteInput} object that provides a custom
+label for the voice input prompt:</p>
+
+<pre class="prettyprint">
+// Key for the string that's delivered in the action's intent
+private static final String EXTRA_VOICE_REPLY = "extra_voice_reply";
+
+String replyLabel = getResources().getString(R.string.reply_label);
+
+RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
+ .setLabel(replyLabel)
+ .build();
+</pre>
+
+
+<h3>Add Pre-defined Text Responses</h3>
+
+<img src="{@docRoot}wear/images/12_voicereply.png" height="200"
+style="float:right;margin:0 0 20px 40px" />
+
+<p>In addition to allowing voice input, you can
+ provide up to five text responses that the user can select for quick replies. Call
+ {@link android.support.v4.app.RemoteInput.Builder#setChoices setChoices()} and pass it a string array.</p>
+
+<p>For example, you can define some responses in a resource array:</p>
+
+<p class="code-caption">res/values/strings.xml</code>
+<pre class="prettyprint">
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string-array name="reply_choices">
+ <item>Yes</item>
+ <item>No</item>
+ <item>Maybe</item>
+ </string-array>
+</resources>
+</pre>
+
+<p>Then, inflate the string array and add it to the
+ {@link android.support.v4.app.RemoteInput}:</p>
+
+<pre>
+public static final EXTRA_VOICE_REPLY = "extra_voice_reply";
+...
+String replyLabel = getResources().getString(R.string.reply_label);
+String[] replyChoices = getResources().getStringArray(R.array.reply_choices);
+
+RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
+ .setLabel(replyLabel)
+ .setChoices(replyChoices)
+ .build();
+</pre>
+
+<h2 id="AddAction">Add the Voice Input as a Notification Action</h2>
+
+<p>
+To set the voice input, attach your
+{@link android.support.v4.app.RemoteInput} object to an action using
+{@link android.support.v4.app.NotificationCompat.Action.Builder#addRemoteInput addRemoteInput()}.
+You can then apply the action to the notification. For example:
+</p>
+
+<pre>
+// Create an intent for the reply action
+Intent replyIntent = new Intent(this, ReplyActivity.class);
+PendingIntent replyPendingIntent =
+ PendingIntent.getActivity(this, 0, replyIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+// Create the reply action and add the remote input
+NotificationCompat.Action action =
+ new NotificationCompat.Action.Builder(R.drawable.ic_reply_icon,
+ getString(R.string.label, replyPendingIntent))
+ .addRemoteInput(remoteInput)
+ .build();
+
+// Build the notification and add the action via WearableExtender
+Notification notification =
+ new NotificationCompat.Builder(mContext)
+ .setSmallIcon(R.drawable.ic_message)
+ .setContentTitle(getString(R.string.title))
+ .setContentText(getString(R.string.content))
+ .extend(new WearableExtender().addAction(action))
+ .build();
+
+// Issue the notification
+NotificationManagerCompat notificationManager =
+ NotificationManagerCompat.from(mContext);
+notificationManager.notify(notificationId, notification);
+</pre>
+<p>
+When you issue this notification, users can swipe to the left to see the "Reply" action button.
+</p>
+
+<h2 id="ReceiveInput">Receiving the Voice Input as a String</h2>
+<p>
+To receive the user's transcribed message in the activity you declared in the reply action's intent,
+call {@link android.support.v4.app.RemoteInput#getResultsFromIntent getResultsFromIntent()},
+passing in the "Reply" action's intent. This method returns a {@link android.os.Bundle} that
+contains the text response. You can then query the {@link android.os.Bundle} to obtain the response.
+
+<p class="note"><b>Note:</b> Do not use {@link android.content.Intent#getExtras Intent.getExtras()}
+to obtain the voice result, because the voice input is stored as
+{@link android.content.ClipData}. The {@link android.support.v4.app.RemoteInput#getResultsFromIntent
+getResultsFromIntent()} method provides a convenient way to receive a character sequence without
+having to process the {@link android.content.ClipData} yourself.
+</p>
+
+<p>
+The following code shows a method that accepts an intent and returns the voice response,
+which is referenced by the <code>EXTRA_VOICE_REPLY</code> key that is used in the previous examples:
+</p>
+
+<pre>
+/**
+ * Obtain the intent that started this activity by calling
+ * Activity.getIntent() and pass it into this method to
+ * get the associated voice input string.
+ */
+
+private CharSequence getMessageText(Intent intent) {
+ Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
+ if (remoteInput != null) {
+ return remoteInput.getCharSequence(EXTRA_VOICE_REPLY);
+ }
+ }
+ return null;
+}
+</pre>
\ No newline at end of file
diff --git a/docs/html/tv/images/atv-framed.png b/docs/html/tv/images/atv-framed.png
new file mode 100644
index 0000000..4cedab2
--- /dev/null
+++ b/docs/html/tv/images/atv-framed.png
Binary files differ
diff --git a/docs/html/tv/images/components.png b/docs/html/tv/images/components.png
new file mode 100644
index 0000000..5984c64
--- /dev/null
+++ b/docs/html/tv/images/components.png
Binary files differ
diff --git a/docs/html/tv/images/hero.jpg b/docs/html/tv/images/hero.jpg
index e951167..e9f8c47 100644
--- a/docs/html/tv/images/hero.jpg
+++ b/docs/html/tv/images/hero.jpg
Binary files differ
diff --git a/docs/html/tv/images/recommendations.png b/docs/html/tv/images/recommendations.png
new file mode 100644
index 0000000..e7aa01d
--- /dev/null
+++ b/docs/html/tv/images/recommendations.png
Binary files differ
diff --git a/docs/html/tv/images/search.png b/docs/html/tv/images/search.png
new file mode 100644
index 0000000..92e1e5f
--- /dev/null
+++ b/docs/html/tv/images/search.png
Binary files differ
diff --git a/docs/html/tv/index.jd b/docs/html/tv/index.jd
index 5c48e49..ff70977 100644
--- a/docs/html/tv/index.jd
+++ b/docs/html/tv/index.jd
@@ -27,11 +27,11 @@
<div class="col-10">
<div class="landing-section-header">
<div class="landing-h1 hero">Android TV</div>
- <div class="landing-subhead hero">Your apps on the big screen</div>
+ <div class="landing-subhead hero">Big screen apps, games, and content</div>
<div class="landing-hero-description">
- <p>Engage users from the comfort of their couches.
- Put your app on TV and bring everyone into
- the action.</p>
+ <p>Recommend great content to users right on the home screen.
+ Enable users to find movies through voice search.
+ Engage users with fluid, immersive games.</p>
</div>
</div>
@@ -56,6 +56,7 @@
<div class="landing-section" style="background-color:#f5f5f5" id="reimagine-your-app">
<div class="wrap">
+
<div class="landing-section-header">
<div class="landing-h1">Reimagine Your App</div>
<div class="landing-subhead">
@@ -63,50 +64,19 @@
</div>
</div>
- <div class="landing-body">
- <div class="landing-breakout cols">
-
- <div class="col-3-wide">
- <img src="{@docRoot}tv/images/placeholder-square.png" alt="">
-
- <p>Simple</p>
- <p class="landing-small">
- Smooth, fast interactions are key to a successful TV app. Keep navigation simple
- and light. Bring your content forward to let users enjoy it with a minimum of
- fuss.
- </p>
- <p class="landing-small">
- <a href="{@docRoot}design/tv/index.html">Learn about TV design</a>
- </p>
- </div>
-
- <div class="col-3-wide">
- <img src="{@docRoot}tv/images/placeholder-square.png" alt="">
-
- <p>Cinematic</p>
- <p class="landing-small">
- What would your app look like if it were a film? Use movement, animation and sound to make your app into an experience.
- </p>
- <p class="landing-small">
- <a href="{@docRoot}design/tv/index.html">Learn about TV design</a>
- </p>
- </div>
-
- <div class="col-3-wide">
- <img src="{@docRoot}tv/images/placeholder-square.png" alt="">
-
- <p>Beautiful</p>
- <p class="landing-small">
- Apps on TV should be a pleasure to look at, as well as enjoyable to use. Use
- made-for-TV styles to make your app familiar and fun.
- </p>
- <p class="landing-small">
- <a href="{@docRoot}design/tv/index.html">Learn about design for TV</a>
- </p>
- </div>
- </div>
-
+ <div class="landing-body landing-align-center">
+ <img src="{@docRoot}tv/images/atv-framed.png" alt="Android TV" >
+ <p>Simple. Cinematic. Beautiful.</p>
+ <p class="landing-small">
+ Smooth, fast interactions are key to a successful TV app. Keep navigation simple
+ and light. <br>Bring your content forward to let users enjoy it with a minimum of
+ effort.
+ </p>
+ <p class="landing-small">
+ <a href="{@docRoot}preview/tv/design/index.html">Learn about design for TV</a>
+ </p>
</div>
+
</div> <!-- end .wrap -->
</div> <!-- end .landing-section -->
@@ -125,7 +95,8 @@
<div class="landing-breakout cols">
<div class="col-3-wide">
- <img src="{@docRoot}tv/images/placeholder-square.png" alt="">
+ <img src="{@docRoot}tv/images/components.png" alt="TV layout components"
+ style="margin-left: 23px;">
<p>Made for TV</p>
<p class="landing-small">
@@ -138,7 +109,8 @@
</div>
<div class="col-3-wide">
- <img src="{@docRoot}tv/images/placeholder-square.png" alt="">
+ <img src="{@docRoot}tv/images/search.png" alt="Search"
+ style="margin-left: 23px;">
<p>Get Found</p>
<p class="landing-small">
@@ -150,7 +122,8 @@
</div>
<div class="col-3-wide">
- <img src="{@docRoot}tv/images/placeholder-square.png" alt="">
+ <img src="{@docRoot}tv/images/recommendations.png" alt="Recommendations"
+ style="margin-left: 23px;">
<p>Recommend</p>
<p class="landing-small">
@@ -168,7 +141,7 @@
</div> <!-- end .wrap -->
</div> <!-- end .landing-section -->
- <div class="landing-section landing-red-background">
+ <div class="landing-section landing-red-background" id="start">
<div class="wrap">
<div class="landing-section-header">
<div class="landing-h1 landing-align-left">Get Started with Android TV</div>
@@ -180,10 +153,10 @@
<div class="landing-body">
<div class="landing-breakout cols">
<div class="col-8" style="margin-left: -8px;">
- <p style="font-size: 24px;">L-Preview SDK</p>
+ <p style="font-size: 24px;">L Developer Preview</p>
<p>
- The preview SDK includes all the tools you need to build and test apps for TV.
- Download it and start creating your big-screen app.
+ Get all the tools you need to build and test TV apps. Download the preview and
+ start creating your big-screen experience.
</p>
</div>
@@ -191,8 +164,9 @@
<div class="col-8">
<p style="font-size: 24px;">ADT-1 Developer Kit</p>
<p>
- Request an ADT-1 Developer Kit, a compact and powerful streaming media player
- and gamepad, ideal for developing and testing apps for TV.
+ Request an <a href="{@docRoot}preview/tv/adt-1/index.html"
+ style="color: white;"><u>ADT-1 Developer Kit</u></a>, a compact and powerful
+ streaming media player and gamepad, ideal for developing and testing apps for TV.
</p>
</div>
@@ -204,14 +178,15 @@
<div class="landing-breakout cols">
<div class="col-8">
- <a href="{@docRoot}preview/setup-sdk.html" class="landing-button landing-primary">
- Download the Preview SDK
+ <a href="{@docRoot}preview/tv/start/index.html" class="landing-button landing-primary">
+ Get Started
</a>
</div>
<div class="col-8">
- <a href="{@docRoot}preview/tv/adt-1/request.html" class="landing-button landing-primary">
- Request ADT-1 Developer Kit
+ <a href="https://support.google.com/googleplay/android-developer/contact/adt_request"
+ class="landing-button landing-primary">
+ Request the ADT-1 Developer Kit
</a>
</div>
</div>
diff --git a/docs/html/wear/images/features/apps.png b/docs/html/wear/images/features/apps.png
new file mode 100644
index 0000000..dbbb5e5
--- /dev/null
+++ b/docs/html/wear/images/features/apps.png
Binary files differ
diff --git a/docs/html/wear/images/features/notifications.png b/docs/html/wear/images/features/notifications.png
new file mode 100644
index 0000000..128853c
--- /dev/null
+++ b/docs/html/wear/images/features/notifications.png
Binary files differ
diff --git a/docs/html/wear/images/features/s1.png b/docs/html/wear/images/features/s1.png
deleted file mode 100644
index ba96cf8..0000000
--- a/docs/html/wear/images/features/s1.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/features/s2.png b/docs/html/wear/images/features/s2.png
deleted file mode 100644
index af28496..0000000
--- a/docs/html/wear/images/features/s2.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/features/s3.png b/docs/html/wear/images/features/s3.png
deleted file mode 100644
index 6ae9868..0000000
--- a/docs/html/wear/images/features/s3.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/features/s4.png b/docs/html/wear/images/features/s4.png
deleted file mode 100644
index 125713d..0000000
--- a/docs/html/wear/images/features/s4.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/features/send-data.png b/docs/html/wear/images/features/send-data.png
new file mode 100644
index 0000000..7010e3f
--- /dev/null
+++ b/docs/html/wear/images/features/send-data.png
Binary files differ
diff --git a/docs/html/wear/images/features/ts1.png b/docs/html/wear/images/features/ts1.png
deleted file mode 100644
index 5d4b1c1..0000000
--- a/docs/html/wear/images/features/ts1.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/features/ts2.png b/docs/html/wear/images/features/ts2.png
deleted file mode 100644
index dc798c5..0000000
--- a/docs/html/wear/images/features/ts2.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/features/ts3.png b/docs/html/wear/images/features/ts3.png
deleted file mode 100644
index 0d68ebc..0000000
--- a/docs/html/wear/images/features/ts3.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/images/features/ts4.png b/docs/html/wear/images/features/voice.png
similarity index 100%
rename from docs/html/wear/images/features/ts4.png
rename to docs/html/wear/images/features/voice.png
Binary files differ
diff --git a/docs/html/wear/images/hero.jpg b/docs/html/wear/images/hero.jpg
index 7850a81..0b4debb 100644
--- a/docs/html/wear/images/hero.jpg
+++ b/docs/html/wear/images/hero.jpg
Binary files differ
diff --git a/docs/html/wear/images/notifications.png b/docs/html/wear/images/notifications.png
new file mode 100644
index 0000000..8b1cb0b
--- /dev/null
+++ b/docs/html/wear/images/notifications.png
Binary files differ
diff --git a/docs/html/wear/images/steps.png b/docs/html/wear/images/steps.png
new file mode 100644
index 0000000..965f382
--- /dev/null
+++ b/docs/html/wear/images/steps.png
Binary files differ
diff --git a/docs/html/wear/images/voicecommands.png b/docs/html/wear/images/voicecommands.png
new file mode 100644
index 0000000..9839ed8
--- /dev/null
+++ b/docs/html/wear/images/voicecommands.png
Binary files differ
diff --git a/docs/html/wear/index.jd b/docs/html/wear/index.jd
index 1de09ee..5dd7690 100644
--- a/docs/html/wear/index.jd
+++ b/docs/html/wear/index.jd
@@ -83,34 +83,35 @@
<div class="landing-body">
<div class="landing-breakout cols">
<div class="col-3-wide">
-
- <div class="landing-inset-video-container">
- <img class="landing-bezel-only" src="{@docRoot}wear/images/screens/bezel.png" alt="">
- <img class="gif" src="{@docRoot}wear/images/screens/reservation_animated.gif">
+ <div class="framed-wear-square">
+ <img src="{@docRoot}wear/images/notifications.png" itemprop="image" alt="">
</div>
-
- <p class="landing-small">
- Say “Ok Google” to ask questions and get stuff done.
- </p>
- </div>
- <div class="col-3-wide">
- <img src="{@docRoot}wear/images/screens/circle_message2.png" itemprop="image" alt="">
+ <br />
<p class="landing-small">
Get glanceable, actionable information at just the right time with notifications
that are synced from your handheld device.
-
</p>
</div>
<div class="col-3-wide">
- <img src="{@docRoot}wear/images/screens/fitness-24.png" alt="">
+ <div class="framed-wear-square">
+ <img src="{@docRoot}wear/images/voicecommands.png">
+ </div>
+ <br />
+ <p class="landing-small">
+ Say “Ok Google” to send messages, ask questions, and get stuff done.
+ </p>
+ </div>
+ <div class="col-3-wide">
+ <div class="framed-wear-square">
+ <img src="{@docRoot}wear/images/steps.png" alt="">
+ </div>
+ <br />
<p class="landing-small">
Design apps that can access a wide range of sensors and other hardware
directly on the wearable.
-
</p>
</div>
</div>
-
<p>
Before you start building, check out the
<a href="{@docRoot}design/wear/index.html">Android Wear Design Principles</a>
@@ -125,15 +126,15 @@
<div class="landing-section-header">
<div class="landing-h1">Developing for Android Wear</div>
<div class="landing-subhead">
- The Android Wear APIs are delivered in the Android v4 support library and Google Play services.
- This lets Android handhelds, old and new, communicate with Android wearables.
- </div>
+ The Android Wear APIs are delivered in the Android Support Library and Google Play
+ services. When using these libraries, handheld devices running Android 4.3
+ or later can communicate with wearables.</div>
</div>
<div class="landing-body">
<div class="landing-breakout cols">
<div class="col-4">
- <img src="{@docRoot}wear/images/features/ts2.png" alt="">
+ <img src="{@docRoot}wear/images/features/notifications.png" alt="">
<p>Synced Notifications</p>
<p class="landing-small">
Notifications on handhelds can automatically sync to wearables, so design them
@@ -144,19 +145,29 @@
</p>
</div>
<div class="col-4">
- <img src="{@docRoot}wear/images/features/ts1.png" alt="">
- <p>Wearable Apps</p>
+ <img src="{@docRoot}wear/images/features/voice.png" alt="">
+ <p>Voice Actions</p>
+ <p class="landing-small">
+ Register your app to handle voice actions, like "Ok Google, take a note,"
+ for a hands-free experience.
+ </p>
+ <p class="landing-small">
+ <a href="{@docRoot}training/wearables/apps/voice.html">Integrate voice actions</a>
+ </p>
+ </div>
+ <div class="col-4">
+ <img src="{@docRoot}wear/images/features/apps.png" alt="">
+ <p>Build Wearable Apps</p>
<p class="landing-small">
Create custom experiences with activities, services, sensors, and much
more with the Android SDK.
</p>
<p class="landing-small">
<a href="{@docRoot}training/wearables/apps/index.html">Create wearable apps</a>
-
</p>
</div>
<div class="col-4">
- <img src="{@docRoot}wear/images/features/ts2.png" alt="">
+ <img src="{@docRoot}wear/images/features/send-data.png" alt="">
<p>Send Data</p>
<p class="landing-small">
Send data and actions between handhelds and wearables with
@@ -167,17 +178,6 @@
</p>
</div>
- <div class="col-4">
- <img src="{@docRoot}wear/images/features/ts4.png" alt="">
- <p>Voice Actions</p>
- <p class="landing-small">
- Register your app to handle voice actions, like "Ok Google, take a note,"
- for a hands-free experience.
- </p>
- <p class="landing-small">
- <a href="{@docRoot}training/wearables/apps/voice-actions.html">Integrate voice actions</a>
- </p>
- </div>
</div>
</div>
</div> <!-- end .wrap -->
@@ -186,10 +186,10 @@
<div class="landing-section landing-white-background">
<div class="wrap">
<div class="landing-section-header">
- <div class="landing-h2">Building an Ecosystem</div>
+ <div class="landing-h1">Building an Ecosystem</div>
<div class="landing-body landing-align-center">
<p class="landing-small">
- We’re working with several partners to bring you watches powered by Android Wear later this year!
+ We’re working with partners around the world to build watches powered by Android Wear!
</p>
</div>
</div>
@@ -286,7 +286,7 @@
</p>
<br>
<p class="landing-small">
- <a target="_blank" href="http://android-developers.blogspot.com/2014/03/android-wear-developer-preview.html">Android Developers Blog</a>
+ <a target="_blank" href="http://android-developers.blogspot.com">Android Developers Blog</a>
</p>
<p></p>
</div>
diff --git a/docs/html/wear/notifications/creating.jd b/docs/html/wear/notifications/creating.jd
deleted file mode 100644
index a5d7da7..0000000
--- a/docs/html/wear/notifications/creating.jd
+++ /dev/null
@@ -1,305 +0,0 @@
-page.title=Creating Notifications for Android Wear
-
-@jd:body
-
-
-<p>When an Android device such as a phone or tablet is connected to an Android wearable,
-all notifications are shared between the devices by default. On the Android wearable, each
-notification appears as a new card in the <a href="{@docRoot}wear/design/user-interface.html#Stream"
->context stream</a>.</p>
-
-<img src="{@docRoot}wear/images/notification_phone@2x.png" width="700" height="265" />
-
-
-<p>So without any effort, your app notifications are available to users on Android Wear.
-However, you can enhance the user experience in several ways. For instance,
-if users may respond to a notification by entering text, such as to reply to
-a message, you can add the ability for users to reply by voice directly from the
-wearable.</p>
-
-<p>To help you provide the best user experience
-for your notifications on Android Wear, this guide shows you how to
-build notifications using standard templates in
-the {@link android.support.v4.app.NotificationCompat.Builder} APIs, plus how to begin
-extending your notification's capabilities for the wearable user experience.</p>
-
-<p class="note"><strong>Note:</strong>
-Notifications using {@link android.widget.RemoteViews} are stripped of custom
-layouts and the system uses only the text and icons in the
-{@link android.app.Notification} object to
-display the notification in a card. However, custom card layouts will be supported by
-the official Android Wear SDK that is coming later.</p>
-</div>
-
-
-
-
-<h2 id="Import">Import the Necessary Classes</h2>
-
-<p>To begin development, you must first complete the instructions in the <a
-href="{@docRoot}wear/preview/start.html">Get Started with the Developer Preview</a> document.
-As mentioned in that document, your app must include
-both the <a href="http://developer.android.com/tools/support-library/features.html#v4">v4 support
-library</a> and the Developer Preview support library. So to get started,
-you should include the following imports in your project code:</p>
-
-<pre>
-import android.support.wearable.notifications.*;
-import android.support.wearable.app.NotificationManagerCompat;
-import android.support.v4.app.NotificationCompat;
-</pre>
-
-<p class="caution"><strong>Caution:</strong>
-The APIs in the current Android Wear Developer Preview are intended for <b>development and testing purposes only</b>, not for production apps. Google may change this Developer Preview significantly prior to the official release of the Android Wear SDK. You may not publicly distribute or ship any application using this Developer Preview, as this Developer Preview will no longer be supported after the official SDK is released (which will cause applications based only on the Developer Preview to break).</p>
-
-
-
-<h2 id="NotificationBuilder">Create Notifications with the Notification Builder</h2>
-
-<p>The <a href="http://developer.android.com/tools/support-library/features.html#v4">v4
-support library</a> allows you to create notifications using the latest notification features
-such as action buttons and large icons, while remaining compatible with Android 1.6 (API level
-4) and higher.</p>
-
-
-<p>For example, here's some code that creates and issues a notification using the
-{@link android.support.v4.app.NotificationCompat} APIs combined with the new
-<a href="{@docRoot}reference/android/support/wearable/app/NotificationManagerCompat.html">
-<code>NotificationManagerCompat</code></a> API:</p>
-
-
-<pre>
-int notificationId = 001;
-// Build intent for notification content
-Intent viewIntent = new Intent(this, ViewEventActivity.class);
-viewIntent.putExtra(EXTRA_EVENT_ID, eventId);
-PendingIntent viewPendingIntent =
- PendingIntent.getActivity(this, 0, viewIntent, 0);
-
-NotificationCompat.Builder notificationBuilder =
- new NotificationCompat.Builder(this)
- .setSmallIcon(R.drawable.ic_event)
- .setContentTitle(eventTitle)
- .setContentText(eventLocation)
- .setContentIntent(viewPendingIntent);
-
-// Get an instance of the NotificationManager service
-NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(this);
-
-// Build the notification and issues it with notification manager.
-notificationManager.notify(notificationId, notificationBuilder.build());
-</pre>
-
-<p>When this notification appears on a handheld device, the user can invoke the
-{@link android.app.PendingIntent}
-specified by the {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent
-setContentIntent()} method by touching the notification. When this
-notification appears on an Android wearable, the user can swipe the notification to the left to
-reveal the <strong>Open</strong> action, which invokes the intent on the handheld device.</p>
-
-
-
-
-
-
-<img src="{@docRoot}wear/images/circle_email_action.png" height="200" style="float:right;clear:right;margin:0 0 20px 60px" />
-
-<h2 id="ActionButtons">Add Action Buttons</h2>
-
-<p>In addition to the primary content action defined by
-{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent
-setContentIntent()}, you can add other actions by passing a {@link android.app.PendingIntent} to
-the {@link android.support.v4.app.NotificationCompat.Builder#addAction
-addAction()} method.</p>
-
-<p>For example, the following code shows the same type of notification from above, but adds an
-action to view the event location on a map.</p>
-
-<pre style="clear:right">
-// Build an intent for an action to view a map
-Intent mapIntent = new Intent(Intent.ACTION_VIEW);
-Uri geoUri = Uri.parse("geo:0,0?q=" + Uri.encode(location));
-mapIntent.setData(geoUri);
-PendingIntent mapPendingIntent =
- PendingIntent.getActivity(this, 0, mapIntent, 0);
-
-NotificationCompat.Builder notificationBuilder =
- new NotificationCompat.Builder(this)
- .setSmallIcon(R.drawable.ic_event)
- .setContentTitle(eventTitle)
- .setContentText(eventLocation)
- .setContentIntent(viewPendingIntent)
- <b>.addAction(R.drawable.ic_map,
- getString(R.string.map), mapPendingIntent);</b>
-</pre>
-
-<p>On a handheld device, the action appears as an
-additional button attached to the notification. On an Android wearable, the action appears as
-a large button when the user swipes the notification to the left. When the user taps the action,
-the associated {@link android.content.Intent} is invoked on the handheld device.</p>
-
-<p class="note"><strong>Tip:</strong> If your notifications includes a "Reply" action
- (such as for a messaging app), you can enhance the behavior by enabling
- voice input replies directly from the Android wearable. For more information, read
- <a href="{@docRoot}wear/notifications/remote-input.html">Receiving Voice Input from a Notification</a>.
-</p>
-
-<p>For details about designing action buttons (including the icon specifications), see the
-<a href="{@docRoot}wear/design/index.html#NotifictionActions">Design Principles of Android
-Wear</a>.</p>
-
-
-<h2 id="BigView">Add a Big View</h2>
-
-<img src="{@docRoot}wear/images/06_images.png" height="200" style="float:right;margin:0 0 20px 40px" />
-
-<p>You can insert extended text content
-to your notification by adding one of the "big view" styles to your notification. On a
-handheld device, users can see the big view content by expanding the notification,
-while on Android Wear, the big view content is visible by default.</p>
-
-<p>To add the extended content to your notification, call {@link
-android.support.v4.app.NotificationCompat.Builder#setStyle setStyle()} on the {@link
-android.support.v4.app.NotificationCompat.Builder} object, passing it an instance of either
-{@link android.support.v4.app.NotificationCompat.BigTextStyle BigTextStyle} or
-{@link android.support.v4.app.NotificationCompat.InboxStyle InboxStyle}.</p>
-
-<p>For example, the following code adds an instance of
-{@link android.support.v4.app.NotificationCompat.BigTextStyle} to the event notification,
-in order to include the complete event description (which includes more text than can fit
-into the space provided for {@link android.support.v4.app.NotificationCompat.Builder#setContentText
-setContentText()}).</p>
-
-
-<pre style="clear:right">
-// Specify the 'big view' content to display the long
-// event description that may not fit the normal content text.
-BigTextStyle bigStyle = new NotificationCompat.BigTextStyle();
-bigStyle.bigText(eventDescription);
-
-NotificationCompat.Builder notificationBuilder =
- new NotificationCompat.Builder(this)
- .setSmallIcon(R.drawable.ic_event)
- .setLargeIcon(BitmapFractory.decodeResource(
- getResources(), R.drawable.notif_background))
- .setContentTitle(eventTitle)
- .setContentText(eventLocation)
- .setContentIntent(viewPendingIntent)
- .addAction(R.drawable.ic_map,
- getString(R.string.map), mapPendingIntent)
- <b>.setStyle(bigStyle);</b>
-</pre>
-
-<p>Notice that you can add a large background image to any notification using the
-{@link android.support.v4.app.NotificationCompat.Builder#setLargeIcon setLargeIcon()}
-method. For more information about designing notifications with large images, see the
-<a href="{@docRoot}wear/design/index.html#Images">Design Principles of Android
-Wear</a>.</p>
-
-
-
-<h2 id="NewFeatures">Add New Features for Wearables</h2>
-
-<p>The Android Wear preview support library provides new APIs that
- allow you to enhance the user experience for notifications on a wearable device. For example,
- you can add additional pages of content that users can view by swiping to the left, or add the ability
-for users to deliver your app a text response using voice input.</p>
-
-<p>To use these new APIs:</p>
-
-<ol>
- <li>Create an instance of
-{@link android.support.v4.app.NotificationCompat.Builder}, setting the
-desired properties for your notification.</li>
- <li>Create a
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#WearableNotificationOptions.Builder(android.content.Context)"> <code>WearableNotificationOptions.Builder</code></a>, setting the wearable-specific options for the notication.</li>
- <li>Call <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#WearableNotificationOptions.Builder#applyTo"><code>WearableNotificationOptions.Builder.applyTo()</code>
- </a>, passing in the {@link android.support.v4.app.NotificationCompat.Builder}. This applies
- the wearable options to the notification.</li>
-</ol>
-
-<p>
-For example, the following code calls the
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#setHintHideIcon(boolean)">
- <code>setHintHideIcon()</code></a> method to remove the app icon from the notification card.
-</p>
-
-<pre>
-// Create a NotificationCompat.Builder for standard notification features
- NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext)
- .setContentTitle("New mail from " + sender)
- .setContentText(subject)
- .setSmallIcon(R.drawable.new_mail);
-// Create a WearablesNotificationOptions.Builder to add functionality for wearables
- Notification notif = new WearableNotificationOptions.Builder()
- <b>.setHintHideIcon(true)</b>
- .build()
- .applyTo(builder); //apply wearable options to to the original notification
- .build()
-</pre>
-
-<p>The
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#setHintHideIcon(boolean)">
- <code>setHintHideIcon()</code></a> method is just one example of new notification features available with the
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html"
- ><code>WearableNotificationOptions.Builder</code></a> class.
-</p>
-
-
-<p>When you want to deliver your notifications, always use the
- <a href="{@docRoot}reference/android/support/wearable/app/NotificationManagerCompat.html">
- <code>NotificationManagerCompat</code></a> API instead of
- {@link android.app.NotificationManager}:</p>
-
-<pre>
-// Get an instance of the NotificationManager service
-NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(this);
-
-// Issue the notification with notification manager.
-notificationManager.notify(notificationId, notif);
-</pre>
-
-
-<p>If you use the framework's {@link android.app.NotificationManager}, some
-features from <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html"><code>WearableNotificationOptions.Builder</code></a>
-do not work.</p>
-
-
-<p>To continue enhancing your notifications for wearables using
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html"
- ><code>WearableNotificationOptions.Builder</code></a> and other APIs in the
- preview support library, see the following developer guides:</p>
-
- <dl>
- <dt><a href="{@docRoot}wear/notifications/remote-input.html">Receiving Voice Input
-from a Notification</a></dt>
- <dd>Add an action that receives voice input from the user and delivers the
-transcribed message to your app.</dd>
- <dt><a href="{@docRoot}wear/notifications/pages.html">Adding Pages to a Notification</a></dt>
- <dd>Add additional pages of information that are visible when the user
-swipes to the left.</dd>
- <dt><a href="{@docRoot}wear/notifications/stacks.html">Stacking Notifications</a></dt>
- <dd>Place all similar notifications from your app in a stack, allowing each to be
-viewed individually without adding multiple cards to the card stream.</dd>
- </dl>
-
-
-<div class="next-docs">
-
-<div class="col-12">
- <h2 class="norule">You might also want to read:</h2>
- <dl>
- <dt><a href="{@docRoot}training/notify-user/index.html">Notifying the User</a></dt>
- <dd>Learn more about how to create notifications.</dd>
- <dt><a href="{@docRoot}guide/components/intents-filters.html">Intents and Intent Filters</a></dt>
- <dd>Learn everything you need to know about the {@link android.content.Intent}
-APIs, used by notificaton actions.</dd>
- </dl>
-</div>
-</div>
-
-
-</body>
-</html>
diff --git a/docs/html/wear/notifications/pages.jd b/docs/html/wear/notifications/pages.jd
deleted file mode 100644
index 7d18b3f..0000000
--- a/docs/html/wear/notifications/pages.jd
+++ /dev/null
@@ -1,65 +0,0 @@
-page.title=Adding Pages to a Notification
-
-@jd:body
-
-
-<img src="{@docRoot}wear/images/09_pages.png" height="200" style="float:right;margin:0 0 20px 40px" />
-<img src="{@docRoot}wear/images/08_pages.png" height="200" style="float:right;margin:0 0 20px 40px" />
-
-<p>When you'd like to provide more information without requiring users
-to open your app on their handheld device, you can
-add one or more pages to the notification on Android Wear. The additional pages
-appear immediately to the right of the main notification card.
-For information about when to use and how to design
-multiple pages, see the
-<a href="{@docRoot}wear/design/index.html#NotificationPages">Design Principles of Android
-Wear</a>.</p>
-
-<p>To create a notification with multiple pages:</p>
-<ol>
- <li>Create the main notification (the first page) the way you'd like the notification to appear on a phone
- or tablet.</li>
- <li>Add pages one at a time with the
-<a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#addPage(android.app.Notification)">
-<code>addPage()</code></a> method, or add multiple pages in a {@link java.util.Collection} with the
-<a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#addPages(java.util.Collection<android.app.Notification>)">
-<code>addPages()</code></a> method.</li>
- <li>Apply the pages to the main notification with the
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.html#applyTo(android.support.v4.app.NotificationCompat.Builder)"
- ><code>applyTo()</code></a> method.</li>
-</ol>
-
-
-<p>For example, here's some code that adds a second page to a notification:</p>
-
-<pre>
-// Create builder for the main notification
-NotificationCompat.Builder notificationBuilder =
- new NotificationCompat.Builder(this)
- .setSmallIcon(R.drawable.new_message)
- .setContentTitle("Page 1")
- .setContentText("Short message")
- .setContentIntent(viewPendingIntent);
-
-// Create a big text style for the second page
-BigTextStyle secondPageStyle = new NotificationCompat.BigTextStyle();
-secondPageStyle.setBigContentTitle("Page 2")
- .bigText("A lot of text...");
-
-// Create second page notification
-Notification secondPageNotification =
- new NotificationCompat.Builder(this)
- .setStyle(secondPageStyle)
- .build();
-
-// Add second page with wearable options and apply to main notification
-Notification twoPageNotification =
- new WearableNotificationsOptions.Builder()
- .addPage(secondPageNotification)
- .build()
- .applyTo(notificationBuilder)
- .build();
-</pre>
-
-</body>
-</html>
diff --git a/docs/html/wear/notifications/remote-input.jd b/docs/html/wear/notifications/remote-input.jd
deleted file mode 100644
index 4db8274..0000000
--- a/docs/html/wear/notifications/remote-input.jd
+++ /dev/null
@@ -1,241 +0,0 @@
-page.title=Receiving Voice Input from a Notification
-
-@jd:body
-
-<img src="{@docRoot}wear/images/13_voicereply.png" height="200" width="169" style="float:right;margin:0 0 20px 40px" />
-
-<img src="{@docRoot}wear/images/03_actions.png" height="200" width="169" style="float:right;margin:0 0 20px 40px" />
-
-<p>If your notification includes an action to respond with text,
- such as to reply to an email, it should normally launch an activity
- on the handheld device. However, when your notification appears on an Android wearable, you can
- allow users to dictate a reply with voice input. You can also provide pre-defined text
- messages for the user to select.</p>
-
-<p>When the user replies with voice or selects one of the available
-messages, the system sends the message to your app on the connected handheld device.
-The message is attached as an extra in the {@link android.content.Intent} you specified
-to be used for the notification action.</p>
-
-<p class="note"><strong>Note:</strong> When developing with the Android emulator,
-you must type text replies into the voice input field, so be sure you have enabled
-<strong>Hardware keyboard present</strong> in the AVD settings.</p>
-
-
-<h2 id="RemoteInput">Define the Remote Input</h2>
-
-<p>To create an action that supports voice input, first create an instance of
- <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html">
-<code>RemoteInput</code></a> using the
- <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.Builder.html"><code>RemoteInput.Builder</code></a> APIs.
- The
- <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.Builder.html"><code>RemoteInput.Builder</code></a> constructor takes a string that the system
- will use as a key for the {@link android.content.Intent} extra that carries the reply message
- to your app on the handheld.</p>
-
-<p>For example, here's how to create a new
- <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html">
-<code>RemoteInput</code></a> object that provides a custom
- label for the voice input prompt:</p>
-
-<pre class="prettyprint">
-// Key for the string that's delivered in the action's intent
-private static final String EXTRA_VOICE_REPLY = "extra_voice_reply";
-
-String replyLabel = getResources().getString(R.string.reply_label);
-
-RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
- .setLabel(replyLabel)
- .build();
-</pre>
-
-
-<h3>Add Pre-defined Text Responses</h3>
-
-<img src="{@docRoot}wear/images/12_voicereply.png" height="200" style="float:right;margin:0 0 20px 40px" />
-
-<p>In addition to allowing voice input, you can
- provide up to five text responses that the user can select for quick replies. Call
- <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.Builder.html#setChoices(java.lang.String[])"><code>setChoices()</code></a> and pass it a string array.</p>
-
-<p>For example, you may define some responses in a resource array:</p>
-
-<p class="code-caption">res/values/strings.xml</code>
-<pre class="prettyprint">
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string-array name="reply_choices">
- <item>Yes</item>
- <item>No</item>
- <item>Maybe</item>
- </string-array>
-</resources>
-</pre>
-
-<p>Then, inflate the string array and add it to the
- <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html"><code>RemoteInput</code></a>:</p>
-
-<pre>
-String replyLabel = getResources().getString(R.string.reply_label);
-String[] replyChoices = getResources().getStringArray(R.array.reply_choices);
-
-RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
- .setLabel(replyLabel)
- .setChoices(replyChoices)
- .build();
-</pre>
-
-
-
-
-<h2 id="PrimaryAction">Receive Voice Input for the Primary Action</h2>
-
-<p>If "Reply" is your notification's primary action (defined by the {@link
-android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()}
-method), then you should attach the
- <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html"><code>RemoteInput</code></a> to the main action using
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#addRemoteInputForContentIntent(android.support.wearable.notifications.RemoteInput)">
-<code>addRemoteInputForContentIntent()</code></a>. For example:</p>
-
-<pre>
-// Create intent for reply action
-Intent replyIntent = new Intent(this, ReplyActivity.class);
-PendingIntent replyPendingIntent =
- PendingIntent.getActivity(this, 0, replyIntent, 0);
-
-// Build the notification
-NotificationCompat.Builder replyNotificationBuilder =
- new NotificationCompat.Builder(this)
- .setSmallIcon(R.drawable.ic_new_message)
- .setContentTitle("Message from Travis")
- .setContentText("I love key lime pie!")
- .setContentIntent(replyPendingIntent);
-
-// Create the remote input
-RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
- .setLabel(replyLabel)
- .build();
-
-// Add remote input to wearable options and apply to notification
-Notification replyNotification =
- new WearableNotificationOptions.Builder()
- .addRemoteInputForContentIntent(remoteInput)
- .build()
- .applyTo(replyNotificationBuilder)
- .build();
-</pre>
-
-<p>By using
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#addRemoteInputForContentIntent(android.support.wearable.notifications.RemoteInput)">
-<code>addRemoteInputForContentIntent()</code></a> to add the
- <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html"><code>RemoteInput</code></a> object to the notification's primary action,
-the button that normally appears as an "Open" action becomes the "Reply" action
-and starts the voice input UI when users select it on Android Wear.</p>
-
-
-
-<h2 id="NewAction">Receive Voice Input for a Secondary Action</h2>
-
-<p>If the "Reply" action is not your notification's primary action and you want to enable
-voice input for a secondary action, add the
- <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html"><code>RemoteInput</code></a> to a new action button defined by an
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableAction.html">
-<code>Action</code></a> object.</p>
-
-<p>You should instantiate the
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableAction.html">
-<code>WearableAction</code></a> with the
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableAction.Builder.html"><code>WearableAction.Builder()</code></a>
-constructor, which takes an icon and text label for the action button, plus the
-{@link android.app.PendingIntent}
-the system should use to invoke your app when the user selects the action. For example:</p>
-
-<pre>
-// Create the pending intent to fire when the user selects the action
-Intent replyIntent = new Intent(this, ReplyActivity.class);
-PendingIntent pendingReplyIntent =
- PendingIntent.getActivity(this, 0, replyIntent, 0);
-
-// Create the remote input
-RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
- .setLabel(replyLabel)
- .build();
-
-// Create the notification action
-WearableAction replyAction = new WearableAction.Builder(R.drawable.ic_message,
- "Reply", pendingIntent)
- .addRemoteInput(remoteInput)
- .build();
-</pre>
-
-
-<p>After you add the
- <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html"><code>RemoteInput</code></a> to the
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableAction.html">
-<code>Wearablection</code></a>, set the
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableAction.html">
-<code>WearableAction</code></a> on the
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html"><code>WearableNotifications.Builder</code></a> using
- <a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationsOptions.Builder.html#addAction(Action)"><code>addAction()</code></a>.
-For example:</p>
-
-<pre>
-// Create basic notification builder
-NotificationCompat.Builder replyNotificationBuilder =
- new NotificationCompat.Builder(this)
- .setContentTitle("New message");
-
-// Create the notification action and add remote input
-WearableAction replyAction = new WearableAction.Builder(R.drawable.ic_message,
- "Reply", pendingIntent)
- .addRemoteInput(remoteInput)
- .build();
-
-// Create wearable notification and add action
-Notification replyNotification =
- new WearableNotificationOptions.Builder()
- .addAction(replyAction)
- .build()
- .applyTo(replyNotificationBuilder)
- .build();
-</pre>
-
-
-<p>Now, when the user selects "Reply" from an Android wearable, the system prompts the user
- for voice input (and shows the list of pre-defined replies, if provided).
- Once the user completes a response, the system invokes
- the {@link android.content.Intent} attached to the action and adds the
-<code>EXTRA_VOICE_REPLY</code> extra (the string
- you passed to the
- <a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.Builder.html"><code>RemoteInput.Builder</code></a> constructor)
- with the user's message as the string value.</p>
-
-<h2 id="ObtainInput">Obtaining the Voice Input as a String</h2>
-<p>To obtain the user's voice input, call
-<a href="{@docRoot}reference/android/support/wearable/notifications/RemoteInput.html#getResultsFromIntent(Intent)"><code>getResultsFromIntent()</code></a>,
-passing in the "Reply" action's intent. This method returns
-a {@link android.os.Bundle} that represents the intent's extras. You can then query the
-{@link android.os.Bundle} to obtain the user's voice input string.
-</p>
-<p>
-The following code shows a method that accepts an intent and returns the voice input string,
-which is referenced by the <code>EXTRA_VOICE_REPLY</code> key that is used in the previous examples:
-</p>
-<pre>
-/**
- * Obtain the intent that started this activity by calling
- * Activity.getIntent() and pass it into this method to
- * get the associated voice input string.
- */
-private String getMessageText(Intent intent) {
- Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
- if (remoteInput != null) {
- return remoteInput.getString(Intent.EXTRA_VOICE_REPLY);
- }
- }
- return null;
-}
-</pre>
-
-</body>
-</html>
diff --git a/docs/html/wear/notifications/stacks.jd b/docs/html/wear/notifications/stacks.jd
deleted file mode 100644
index 3c3dc09..0000000
--- a/docs/html/wear/notifications/stacks.jd
+++ /dev/null
@@ -1,138 +0,0 @@
-page.title=Stacking Notifications
-
-@jd:body
-
-<img src="{@docRoot}wear/images/11_bundles_B.png" height="200" width="169" style="float:right;margin:0 0 20px 40px" alt="" />
-<img src="{@docRoot}wear/images/11_bundles_A.png" height="200" width="169" style="float:right;margin:0 0 20px 40px" alt="" />
-
-<p>When creating notifications for a handheld device, you should always aggregate similar
-notifications into a single summary notification. For example, if your app creates notifications
-for received messages, you should not show more than one notification
-on a handheld device—when more than one is message is received, use a single notification
-to provide a summary such as "2 new messages."</p>
-
-<p>However, a summary notification is less useful on an Android wearable because users
-are not able to read details from each message on the wearable (they must open your app on the
-handheld to view more information). So for the wearable device, you should
-group all the notifications together in a stack. The stack of notifications appears as a single
-card, which users can expand to view the details from each notification separately. The new
-<a href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#setGroup(java.lang.String, int)">
-<code>setGroup()</code></a> method makes this possible while allowing you to still provide
-only one summary notification on the handheld device.</p>
-
-<p>For details about designing notification stacks, see the
-<a href="{@docRoot}wear/design/index.html#NotificationStacks">Design Principles of Android
-Wear</a>.</p>
-
-
-<h2 id="AddGroup">Add Each Notification to a Group</h2>
-
-<p>To create a stack, call <a
-href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#setGroup(java.lang.String, int)">
-<code>setGroup()</code></a> for each notification you want in the stack and specify a
-group key. Then call <a href="{@docRoot}reference/android/support/wearable/app/NotificationManagerCompat.html#notify(int, android.app.Notification)"><code>notify()</code></a> to send it to the wearable.</p>
-
-<pre style="clear:right">
-final static String GROUP_KEY_EMAILS = "group_key_emails";
-
-// Build the notification
-NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext)
- .setContentTitle("New mail from " + sender1)
- .setContentText(subject1)
- .setSmallIcon(R.drawable.new_mail);
-
-// Set the group with WearableNotificationOptions.Builder and apply to the notification
-Notification notif1 = new WearableNotificationOptions.Builder()
- .setGroup(GROUP_KEY_EMAILS)
- .build()
- .applyTo(builder)
- .build();
-
-// Issue the notification
-NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(this);
-notificationManager.notify(notificationId1, notif);
-</pre>
-
-<p>Later on, when you create another notification, specify
-the same group key. When you call
-<a href="{@docRoot}reference/android/support/v4/app/NotificationManagerCompat.html#notify(int, android.app.Notification)"><code>notify()</code></a>,
-this notification appears in the same stack as the previous notification,
-instead of as a new card:</p>
-
-<pre style="clear:right">
-builder = new NotificationCompat.Builder(mContext)
- .setContentTitle("New mail from " + sender2)
- .setContentText(subject2)
- .setSmallIcon(R.drawable.new_mail);
-
-// Use the same group as the previous notification
-Notification notif2 = new WearableNotificationOptions.Builder()
- .setGroup(GROUP_KEY_EMAILS)
- .build()
- .applyTo(builder)
- .build();
-
-notificationManager.notify(notificationId2, notif);
-</pre>
-
-<p>By default, notifications appear in the order in which you added them, with the most recent
- notification visible at the top. You can define a specific position in the group
- by passing an order position as the second parameter for <a
-href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationOptions.Builder.html#setGroup(java.lang.String, int)">
-<code>setGroup()</code></a>.</p>
-
-
-<h2 id="AddSummary">Add a Summary Notification</h2>
-
-<img src="{@docRoot}wear/images/notif_summary_framed.png" height="242" width="330" style="float:right;margin:0 0 20px 40px" alt="" />
-
-<p>It's important that you still provide a summary notification that appears on handheld devices.
-So in addition to adding each unique notification to the same stack group, also add a summary
-notification, but set its order position to be <a
-href="{@docRoot}reference/android/support/wearable/notifications/WearableNotificationsOptions.html#GROUP_ORDER_SUMMARY"><code>GROUP_ORDER_SUMMARY</code></a>.</p>
-
-<p>This notification does not appear in your stack of notifications on the wearable, but
-appears as the only notification on the handheld device.</p>
-
-<pre style="clear:right">
-Bitmap largeIcon = BitmapFactory.decodeResource(getResources(),
- R.drawable.ic_large_icon);
-
-// Create an InboxStyle notification
-builder = new NotificationCompat.Builder(this)
- .setContentTitle("2 new messages")
- .setSmallIcon(R.drawable.ic_small_icon)
- .setLargeIcon(largeIcon)
- .setStyle(new NotificationCompat.InboxStyle()
- .addLine("Alex Faaborg Check this out")
- .addLine("Jeff Chang Launch Party")
- .setBigContentTitle("2 new messages")
- .setSummaryText("johndoe@gmail.com"));
-
-// Specify the notification to be the group summary
-Notification summaryNotification = new WearableNotificationOptions.Builder()
- .setGroupSummary(GROUP_KEY_EMAILS)
- .build()
- .applyTo(builder)
- .build();
-
-notificationManager.notify(notificationId3, summaryNotification);
-</pre>
-
-<p>
-This notification uses {@link android.support.v4.app.NotificationCompat.InboxStyle},
-which gives you an easy way to create notifications for email or messaging apps.
-You can use this style, another one defined in {@link android.support.v4.app.NotificationCompat},
-or no style for the summary notification.
-</p>
-
-<p class="note"><b>Tip:</b>
-To style the text like in the example screenshot, see
-<a href="{@docRoot}guide/topics/resources/string-resource.html#StylingWithHTML">Styling
-with HTML markup</a> and
-<a href="{@docRoot}guide/topics/resources/string-resource.html#StylingWithSpannables">Styling
-with Spannables</a>.
-</p>
-</body>
-</html>
\ No newline at end of file
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 17ce026..72e39bb 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1773,46 +1773,6 @@
}
/**
- * Return the glyph Ids for the characters in the string.
- *
- * @param text The text to measure
- * @param start The index of the first char to to measure
- * @param end The end of the text slice to measure
- * @param contextStart the index of the first character to use for shaping context,
- * must be <= start
- * @param contextEnd the index past the last character to use for shaping context,
- * must be >= end
- * @param flags the flags to control the advances, either {@link #DIRECTION_LTR}
- * or {@link #DIRECTION_RTL}
- * @param glyphs array to receive the glyph Ids of the characters.
- * Must be at least a large as the text.
- * @return the number of glyphs in the returned array
- *
- * @hide
- *
- * Used only for BiDi / RTL Tests
- */
- public int getTextGlyphs(String text, int start, int end, int contextStart, int contextEnd,
- int flags, char[] glyphs) {
- if (text == null) {
- throw new IllegalArgumentException("text cannot be null");
- }
- if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
- throw new IllegalArgumentException("unknown flags value: " + flags);
- }
- if ((start | end | contextStart | contextEnd | (end - start)
- | (start - contextStart) | (contextEnd - end) | (text.length() - end)
- | (text.length() - contextEnd)) < 0) {
- throw new IndexOutOfBoundsException();
- }
- if (end - start > glyphs.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
- return native_getTextGlyphs(mNativePaint, text, start, end, contextStart, contextEnd,
- flags, glyphs);
- }
-
- /**
* Convenience overload that takes a char array instead of a
* String.
*
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 0862cdd..17795f3 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -126,7 +126,34 @@
*/
public SurfaceTexture(int texName, boolean singleBufferMode) {
mCreatorLooper = Looper.myLooper();
- nativeInit(texName, singleBufferMode, new WeakReference<SurfaceTexture>(this));
+ nativeInit(false, texName, singleBufferMode, new WeakReference<SurfaceTexture>(this));
+ }
+
+ /**
+ * Construct a new SurfaceTexture to stream images to a given OpenGL texture.
+ *
+ * In single buffered mode the application is responsible for serializing access to the image
+ * content buffer. Each time the image content is to be updated, the
+ * {@link #releaseTexImage()} method must be called before the image content producer takes
+ * ownership of the buffer. For example, when producing image content with the NDK
+ * ANativeWindow_lock and ANativeWindow_unlockAndPost functions, {@link #releaseTexImage()}
+ * must be called before each ANativeWindow_lock, or that call will fail. When producing
+ * image content with OpenGL ES, {@link #releaseTexImage()} must be called before the first
+ * OpenGL ES function call each frame.
+ *
+ * Unlike {@link #SurfaceTexture(int, boolean)}, which takes an OpenGL texture object name,
+ * this constructor creates the SurfaceTexture in detached mode. A texture name must be passed
+ * in using {@link #attachToGLContext} before calling {@link #releaseTexImage()} and producing
+ * image content using OpenGL ES.
+ *
+ * @param singleBufferMode whether the SurfaceTexture will be in single buffered mode.
+ *
+ * @throws Surface.OutOfResourcesException If the SurfaceTexture cannot be created.
+ * @hide
+ */
+ public SurfaceTexture(boolean singleBufferMode) {
+ mCreatorLooper = Looper.myLooper();
+ nativeInit(true, 0, singleBufferMode, new WeakReference<SurfaceTexture>(this));
}
/**
@@ -339,8 +366,8 @@
}
}
- private native void nativeInit(int texName, boolean singleBufferMode,
- WeakReference<SurfaceTexture> weakSelf)
+ private native void nativeInit(boolean isDetached, int texName,
+ boolean singleBufferMode, WeakReference<SurfaceTexture> weakSelf)
throws Surface.OutOfResourcesException;
private native void nativeFinalize();
private native void nativeGetTransformMatrix(float[] mtx);
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index cb48de2..0dc903a 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -117,7 +117,7 @@
if (sSystemFontMap != null) {
return create(sSystemFontMap.get(familyName), style);
}
- return new Typeface(nativeCreate(familyName, style));
+ return null;
}
/**
@@ -183,11 +183,9 @@
if (fontFamily.addFontFromAsset(mgr, path)) {
FontFamily[] families = { fontFamily };
return createFromFamiliesWithDefault(families);
- } else {
- return null;
}
}
- return new Typeface(nativeCreateFromAsset(mgr, path));
+ return null;
}
/**
@@ -212,11 +210,9 @@
if (fontFamily.addFont(path)) {
FontFamily[] families = { fontFamily };
return createFromFamiliesWithDefault(families);
- } else {
- return null;
}
}
- return new Typeface(nativeCreateFromFile(path));
+ return null;
}
/**
@@ -383,12 +379,9 @@
return result;
}
- private static native long nativeCreate(String familyName, int style);
private static native long nativeCreateFromTypeface(long native_instance, int style);
private static native void nativeUnref(long native_instance);
private static native int nativeGetStyle(long native_instance);
- private static native long nativeCreateFromAsset(AssetManager mgr, String path);
- private static native long nativeCreateFromFile(String path);
private static native long nativeCreateFromArray(long[] familyArray);
private static native void nativeSetDefault(long native_instance);
}
diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 0740761..cef3377 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -80,20 +80,41 @@
*/
public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable {
private final AnimationState mAnimationState;
+
+ /** The current frame, may be -1 when not animating. */
private int mCurFrame = -1;
+
+ /** Whether the drawable has an animation callback posted. */
+ private boolean mRunning;
+
+ /** Whether the drawable should animate when visible. */
private boolean mAnimating;
+
private boolean mMutated;
public AnimationDrawable() {
this(null, null);
}
+ /**
+ * Sets whether this AnimationDrawable is visible.
+ * <p>
+ * When the drawable becomes invisible, it will pause its animation. A
+ * subsequent change to visible with <code>restart</code> set to true will
+ * restart the animation from the first frame. If <code>restart</code> is
+ * false, the animation will resume from the most recent frame.
+ *
+ * @param visible true if visible, false otherwise
+ * @param restart when visible, true to force the animation to restart
+ * from the first frame
+ * @return true if the new visibility is different than its previous state
+ */
@Override
public boolean setVisible(boolean visible, boolean restart) {
final boolean changed = super.setVisible(visible, restart);
if (visible) {
- if (changed || restart) {
- setFrame(0, true, mAnimating);
+ if (restart || changed) {
+ setFrame(restart ? 0 : mCurFrame, true, mAnimating);
}
} else {
unscheduleSelf(this);
@@ -115,6 +136,8 @@
*/
@Override
public void start() {
+ mAnimating = true;
+
if (!isRunning()) {
run();
}
@@ -129,6 +152,8 @@
*/
@Override
public void stop() {
+ mAnimating = false;
+
if (isRunning()) {
unscheduleSelf(this);
}
@@ -141,7 +166,7 @@
*/
@Override
public boolean isRunning() {
- return mAnimating;
+ return mRunning;
}
/**
@@ -158,7 +183,7 @@
@Override
public void unscheduleSelf(Runnable what) {
mCurFrame = -1;
- mAnimating = false;
+ mRunning = false;
super.unscheduleSelf(what);
}
@@ -219,6 +244,7 @@
if (next >= N) {
next = 0;
}
+
setFrame(next, unschedule, !mAnimationState.mOneShot || next < (N - 1));
}
@@ -226,6 +252,7 @@
if (frame >= mAnimationState.getChildCount()) {
return;
}
+ mAnimating = animate;
mCurFrame = frame;
selectDrawable(frame);
if (unschedule || animate) {
@@ -234,7 +261,7 @@
if (animate) {
// Unscheduling may have clobbered these values; restore them
mCurFrame = frame;
- mAnimating = true;
+ mRunning = true;
scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
}
}
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 38b8aaf..ed44cde 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -16,12 +16,14 @@
package android.graphics.drawable;
+import android.annotation.NonNull;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Insets;
+import android.graphics.Outline;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.PorterDuff.Mode;
@@ -123,6 +125,14 @@
}
@Override
+ public boolean getOutline(@NonNull Outline outline) {
+ if (mCurrDrawable != null) {
+ return mCurrDrawable.getOutline(outline);
+ }
+ return false;
+ }
+
+ @Override
public void setAlpha(int alpha) {
if (!mHasAlpha || mAlpha != alpha) {
mHasAlpha = true;
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 5cea7c9..2f22392 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -16,12 +16,14 @@
package android.graphics.drawable;
+import android.annotation.NonNull;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
+import android.graphics.Outline;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
@@ -563,6 +565,16 @@
}
}
+ /**
+ * Builds an Outline from the first child Drawable, if present.
+ */
+ @Override
+ public boolean getOutline(@NonNull Outline outline) {
+ if (mLayerState.mNum < 1) return false;
+ final Drawable firstChild = mLayerState.mChildren[0].mDrawable;
+ return firstChild.getOutline(outline);
+ }
+
@Override
public void setHotspot(float x, float y) {
final ChildDrawable[] array = mLayerState.mChildren;
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index f2e75a5..9bf7c43 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -16,6 +16,8 @@
package android.graphics.drawable;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
@@ -52,7 +54,7 @@
* <code><!-- A red ripple masked against an opaque rectangle. --/>
* <ripple android:color="#ffff0000">
* <item android:id="@android:id/mask"
- * android:drawable="#ffffffff" />
+ * android:drawable="@android:color/white" />
* <ripple /></code>
* </pre>
* <p>
@@ -62,9 +64,9 @@
* If no mask layer is set, the ripple effect is masked against the composite
* of the child layers.
* <pre>
- * <code><!-- A blue ripple drawn atop a green rectangle. --/>
+ * <code><!-- A blue ripple drawn atop a black rectangle. --/>
* <ripple android:color="#ff00ff00">
- * <item android:drawable="#ff0000ff" />
+ * <item android:drawable="@android:color/black" />
* <ripple />
*
* <!-- A red ripple drawn atop a drawable resource. --/>
@@ -147,11 +149,7 @@
}
/**
- * Creates a new ripple drawable with the specified content and mask
- * drawables.
- *
- * @param content The content drawable, may be {@code null}
- * @param mask The mask drawable, may be {@code null}
+ * @hide TO BE REMOVED FOR L-RELEASE
*/
public RippleDrawable(Drawable content, Drawable mask) {
this(new RippleState(null, null, null), null, null);
@@ -165,6 +163,36 @@
}
ensurePadding();
+
+ Log.e(LOG_TAG, "This constructor is being removed", new RuntimeException());
+ }
+
+ /**
+ * Creates a new ripple drawable with the specified ripple color and
+ * optional content and mask drawables.
+ *
+ * @param color The ripple color
+ * @param content The content drawable, may be {@code null}
+ * @param mask The mask drawable, may be {@code null}
+ */
+ public RippleDrawable(@NonNull ColorStateList color, @Nullable Drawable content,
+ @Nullable Drawable mask) {
+ this(new RippleState(null, null, null), null, null);
+
+ if (color == null) {
+ throw new IllegalArgumentException("RippleDrawable requires a non-null color");
+ }
+
+ if (content != null) {
+ addLayer(content, null, 0, 0, 0, 0, 0);
+ }
+
+ if (mask != null) {
+ addLayer(content, null, android.R.id.mask, 0, 0, 0, 0);
+ }
+
+ setColor(color);
+ ensurePadding();
}
@Override
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 95d8a97..1a96b2f 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -13,6 +13,7 @@
font/Font.cpp \
AmbientShadow.cpp \
Animator.cpp \
+ AnimatorManager.cpp \
AssetAtlas.cpp \
DamageAccumulator.cpp \
FontRenderer.cpp \
@@ -71,8 +72,6 @@
intermediates := $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,)
LOCAL_C_INCLUDES += \
- $(JNI_H_INCLUDE) \
- $(LOCAL_PATH)/../../include/utils \
external/skia/src/core
LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index dc6d852..4a8c122 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -18,6 +18,7 @@
#include "Animator.h"
+#include <inttypes.h>
#include <set>
#include "RenderNode.h"
@@ -35,72 +36,105 @@
, mDeltaValue(0)
, mFromValue(0)
, mInterpolator(0)
- , mPlayState(NEEDS_START)
+ , mStagingPlayState(NOT_STARTED)
+ , mPlayState(NOT_STARTED)
+ , mHasStartValue(false)
, mStartTime(0)
- , mDelayUntil(0)
, mDuration(300)
, mStartDelay(0) {
-
}
BaseRenderNodeAnimator::~BaseRenderNodeAnimator() {
- setInterpolator(NULL);
+ delete mInterpolator;
+}
+
+void BaseRenderNodeAnimator::checkMutable() {
+ // Should be impossible to hit as the Java-side also has guards for this
+ LOG_ALWAYS_FATAL_IF(mStagingPlayState != NOT_STARTED,
+ "Animator has already been started!");
}
void BaseRenderNodeAnimator::setInterpolator(Interpolator* interpolator) {
+ checkMutable();
delete mInterpolator;
mInterpolator = interpolator;
}
void BaseRenderNodeAnimator::setStartValue(float value) {
- LOG_ALWAYS_FATAL_IF(mPlayState != NEEDS_START,
- "Cannot set the start value after the animator has started!");
- mFromValue = value;
- mDeltaValue = (mFinalValue - mFromValue);
- mPlayState = PENDING;
+ checkMutable();
+ doSetStartValue(value);
}
-void BaseRenderNodeAnimator::setupStartValueIfNecessary(RenderNode* target, TreeInfo& info) {
- if (mPlayState == NEEDS_START) {
- setStartValue(getValue(target));
- }
+void BaseRenderNodeAnimator::doSetStartValue(float value) {
+ mFromValue = value;
+ mDeltaValue = (mFinalValue - mFromValue);
+ mHasStartValue = true;
}
void BaseRenderNodeAnimator::setDuration(nsecs_t duration) {
+ checkMutable();
mDuration = duration;
}
void BaseRenderNodeAnimator::setStartDelay(nsecs_t startDelay) {
+ checkMutable();
mStartDelay = startDelay;
}
-bool BaseRenderNodeAnimator::animate(RenderNode* target, TreeInfo& info) {
- if (mPlayState == PENDING && mStartDelay > 0 && mDelayUntil == 0) {
- mDelayUntil = info.frameTimeMs + mStartDelay;
- return false;
+void BaseRenderNodeAnimator::pushStaging(RenderNode* target, TreeInfo& info) {
+ if (!mHasStartValue) {
+ doSetStartValue(getValue(target));
}
-
- if (mDelayUntil > info.frameTimeMs) {
- return false;
- }
-
- if (mPlayState == PENDING) {
- mPlayState = RUNNING;
- mStartTime = info.frameTimeMs;
- // No interpolator was set, use the default
- if (!mInterpolator) {
- setInterpolator(Interpolator::createDefaultInterpolator());
+ if (mStagingPlayState > mPlayState) {
+ mPlayState = mStagingPlayState;
+ // Oh boy, we're starting! Man the battle stations!
+ if (mPlayState == RUNNING) {
+ transitionToRunning(info);
}
}
+}
+
+void BaseRenderNodeAnimator::transitionToRunning(TreeInfo& info) {
+ LOG_ALWAYS_FATAL_IF(info.frameTimeMs <= 0, "%" PRId64 " isn't a real frame time!", info.frameTimeMs);
+ if (mStartDelay < 0 || mStartDelay > 50000) {
+ ALOGW("Your start delay is strange and confusing: %" PRId64, mStartDelay);
+ }
+ mStartTime = info.frameTimeMs + mStartDelay;
+ if (mStartTime < 0) {
+ ALOGW("Ended up with a really weird start time of %" PRId64
+ " with frame time %" PRId64 " and start delay %" PRId64,
+ mStartTime, info.frameTimeMs, mStartDelay);
+ // Set to 0 so that the animate() basically instantly finishes
+ mStartTime = 0;
+ }
+ // No interpolator was set, use the default
+ if (!mInterpolator) {
+ setInterpolator(Interpolator::createDefaultInterpolator());
+ }
+ if (mDuration < 0 || mDuration > 50000) {
+ ALOGW("Your duration is strange and confusing: %" PRId64, mDuration);
+ }
+}
+
+bool BaseRenderNodeAnimator::animate(RenderNode* target, TreeInfo& info) {
+ if (mPlayState < RUNNING) {
+ return false;
+ }
+
+ if (mStartTime > info.frameTimeMs) {
+ info.out.hasAnimations |= true;
+ return false;
+ }
float fraction = 1.0f;
- if (mPlayState == RUNNING) {
- fraction = mDuration > 0 ? (float)(info.frameTimeMs - mStartTime) / mDuration : 1.0f;
- if (fraction >= 1.0f) {
- fraction = 1.0f;
- mPlayState = FINISHED;
- }
+ if (mPlayState == RUNNING && mDuration > 0) {
+ fraction = (float)(info.frameTimeMs - mStartTime) / mDuration;
}
+ if (fraction >= 1.0f) {
+ fraction = 1.0f;
+ mPlayState = FINISHED;
+ }
+
fraction = mInterpolator->interpolate(fraction);
setValue(target, mFromValue + (mDeltaValue * fraction));
@@ -108,6 +142,8 @@
callOnFinishedListener(info);
return true;
}
+
+ info.out.hasAnimations |= true;
return false;
}
@@ -153,7 +189,7 @@
}
void RenderPropertyAnimator::onAttached(RenderNode* target) {
- if (mPlayState == NEEDS_START
+ if (!mHasStartValue
&& target->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
setStartValue((target->stagingProperties().*mPropertyAccess->getter)());
}
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index 6cb72c4c..a981b5a 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -50,12 +50,11 @@
ANDROID_API void setListener(AnimationListener* listener) {
mListener = listener;
}
+ ANDROID_API void start() { mStagingPlayState = RUNNING; }
+ ANDROID_API void cancel() { mStagingPlayState = FINISHED; }
- ANDROID_API virtual void onAttached(RenderNode* target) {}
-
- // Guaranteed to happen before the staging push
- void setupStartValueIfNecessary(RenderNode* target, TreeInfo& info);
-
+ virtual void onAttached(RenderNode* target) {}
+ virtual void pushStaging(RenderNode* target, TreeInfo& info);
bool animate(RenderNode* target, TreeInfo& info);
bool isFinished() { return mPlayState == FINISHED; }
@@ -73,8 +72,7 @@
void callOnFinishedListener(TreeInfo& info);
enum PlayState {
- NEEDS_START,
- PENDING,
+ NOT_STARTED,
RUNNING,
FINISHED,
};
@@ -84,13 +82,19 @@
float mFromValue;
Interpolator* mInterpolator;
+ PlayState mStagingPlayState;
PlayState mPlayState;
+ bool mHasStartValue;
nsecs_t mStartTime;
- nsecs_t mDelayUntil;
nsecs_t mDuration;
nsecs_t mStartDelay;
sp<AnimationListener> mListener;
+
+private:
+ void doSetStartValue(float value);
+ inline void checkMutable();
+ void transitionToRunning(TreeInfo& info);
};
class RenderPropertyAnimator : public BaseRenderNodeAnimator {
@@ -112,7 +116,7 @@
ANDROID_API RenderPropertyAnimator(RenderProperty property, float finalValue);
- ANDROID_API virtual void onAttached(RenderNode* target);
+ virtual void onAttached(RenderNode* target);
ANDROID_API virtual uint32_t dirtyMask();
diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp
new file mode 100644
index 0000000..6a10cf8
--- /dev/null
+++ b/libs/hwui/AnimatorManager.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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 "AnimatorManager.h"
+
+#include <algorithm>
+
+#include "RenderNode.h"
+
+namespace android {
+namespace uirenderer {
+
+using namespace std;
+
+static void unref(BaseRenderNodeAnimator* animator) {
+ animator->decStrong(0);
+}
+
+AnimatorManager::AnimatorManager(RenderNode& parent)
+ : mParent(parent) {
+}
+
+AnimatorManager::~AnimatorManager() {
+ for_each(mNewAnimators.begin(), mNewAnimators.end(), unref);
+ for_each(mAnimators.begin(), mAnimators.end(), unref);
+}
+
+void AnimatorManager::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
+ animator->incStrong(0);
+ animator->onAttached(&mParent);
+ mNewAnimators.push_back(animator.get());
+}
+
+template<typename T>
+static void move_all(T& source, T& dest) {
+ dest.reserve(source.size() + dest.size());
+ for (typename T::iterator it = source.begin(); it != source.end(); it++) {
+ dest.push_back(*it);
+ }
+ source.clear();
+}
+
+void AnimatorManager::pushStaging(TreeInfo& info) {
+ if (mNewAnimators.size()) {
+ // Since this is a straight move, we don't need to inc/dec the ref count
+ move_all(mNewAnimators, mAnimators);
+ }
+ for (vector<BaseRenderNodeAnimator*>::iterator it = mAnimators.begin(); it != mAnimators.end(); it++) {
+ (*it)->pushStaging(&mParent, info);
+ }
+}
+
+class AnimateFunctor {
+public:
+ AnimateFunctor(RenderNode& target, TreeInfo& info)
+ : mTarget(target), mInfo(info) {}
+
+ bool operator() (BaseRenderNodeAnimator* animator) {
+ bool remove = animator->animate(&mTarget, mInfo);
+ if (remove) {
+ animator->decStrong(0);
+ }
+ return remove;
+ }
+private:
+ RenderNode& mTarget;
+ TreeInfo& mInfo;
+};
+
+void AnimatorManager::animate(TreeInfo& info) {
+ if (!mAnimators.size()) return;
+
+ // TODO: Can we target this better? For now treat it like any other staging
+ // property push and just damage self before and after animators are run
+
+ mParent.damageSelf(info);
+ info.damageAccumulator->popTransform();
+
+ AnimateFunctor functor(mParent, info);
+ std::vector< BaseRenderNodeAnimator* >::iterator newEnd;
+ newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
+ mAnimators.erase(newEnd, mAnimators.end());
+
+ mParent.mProperties.updateMatrix();
+ info.damageAccumulator->pushTransform(&mParent);
+ mParent.damageSelf(info);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/AnimatorManager.h b/libs/hwui/AnimatorManager.h
new file mode 100644
index 0000000..2568121
--- /dev/null
+++ b/libs/hwui/AnimatorManager.h
@@ -0,0 +1,57 @@
+/*
+ * 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 ANIMATORMANAGER_H
+#define ANIMATORMANAGER_H
+
+#include <vector>
+
+#include <cutils/compiler.h>
+#include <utils/StrongPointer.h>
+
+#include "TreeInfo.h"
+#include "utils/Macros.h"
+
+namespace android {
+namespace uirenderer {
+
+class BaseRenderNodeAnimator;
+class RenderNode;
+
+// Responsible for managing the animators for a single RenderNode
+class AnimatorManager {
+ PREVENT_COPY_AND_ASSIGN(AnimatorManager);
+public:
+ AnimatorManager(RenderNode& parent);
+ ~AnimatorManager();
+
+ void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
+
+ void pushStaging(TreeInfo& info);
+ void animate(TreeInfo& info);
+
+private:
+ RenderNode& mParent;
+
+ // To improve the efficiency of resizing & removing from the vector
+ // use manual ref counting instead of sp<>.
+ std::vector<BaseRenderNodeAnimator*> mNewAnimators;
+ std::vector<BaseRenderNodeAnimator*> mAnimators;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* ANIMATORMANAGER_H */
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index 8e99b9a..02b0372 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -58,7 +58,7 @@
SkRefCnt_SafeAssign(mColorFilter, colorFilter);
}
-bool DeferredLayerUpdater::apply(TreeInfo& info) {
+bool DeferredLayerUpdater::apply() {
bool success = true;
// These properties are applied the same to both layer types
mLayer->setColorFilter(mColorFilter);
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index c76bd5e..5905b95 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -75,7 +75,7 @@
ANDROID_API void setPaint(const SkPaint* paint);
- ANDROID_API bool apply(TreeInfo& info);
+ ANDROID_API bool apply();
ANDROID_API Layer* backingLayer() {
return mLayer;
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 5ed04a0..9fb972b 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -253,7 +253,7 @@
// default empty constructor for bounds, to be overridden in child constructor body
DrawBoundedOp(const SkPaint* paint): DrawOp(paint) { }
- bool getLocalBounds(Rect& localBounds) {
+ virtual bool getLocalBounds(Rect& localBounds) {
localBounds.set(mLocalBounds);
OpenGLRenderer::TextShadow textShadow;
if (OpenGLRenderer::getTextShadow(mPaint, &textShadow)) {
@@ -1029,7 +1029,7 @@
DrawStrokableOp(float left, float top, float right, float bottom, const SkPaint* paint)
: DrawBoundedOp(left, top, right, bottom, paint) {};
- bool getLocalBounds(Rect& localBounds) {
+ virtual bool getLocalBounds(Rect& localBounds) {
localBounds.set(mLocalBounds);
if (mPaint && mPaint->getStyle() != SkPaint::kFill_Style) {
localBounds.outset(strokeWidthOutset());
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 4ff5780..8a1aebc 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -184,12 +184,6 @@
virtual status_t drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, const SkPaint* paint);
virtual status_t drawCircle(float x, float y, float radius, const SkPaint* paint);
- virtual status_t drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y,
- CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) {
- // TODO: Remove once android_view_GLES20Canvas uses DisplayListRenderer
- // directly
- return drawCircle(x->value, y->value, radius->value, &paint->value);
- }
virtual status_t drawOval(float left, float top, float right, float bottom,
const SkPaint* paint);
virtual status_t drawArc(float left, float top, float right, float bottom,
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 131384a..e803ec3 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -62,7 +62,7 @@
, mNeedsDisplayListDataSync(false)
, mDisplayListData(0)
, mStagingDisplayListData(0)
- , mNeedsAnimatorsSync(false)
+ , mAnimatorManager(*this)
, mLayer(0) {
}
@@ -117,6 +117,10 @@
prepareTreeImpl(info);
}
+void RenderNode::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
+ mAnimatorManager.addAnimator(animator);
+}
+
void RenderNode::damageSelf(TreeInfo& info) {
if (isRenderable()) {
if (properties().getClipDamageToBounds()) {
@@ -193,11 +197,11 @@
info.damageAccumulator->pushTransform(this);
if (info.mode == TreeInfo::MODE_FULL) {
pushStagingPropertiesChanges(info);
- evaluateAnimations(info);
+ mAnimatorManager.animate(info);
} else if (info.mode == TreeInfo::MODE_MAYBE_DETACHING) {
pushStagingPropertiesChanges(info);
} else if (info.mode == TreeInfo::MODE_RT_ONLY) {
- evaluateAnimations(info);
+ mAnimatorManager.animate(info);
}
prepareLayer(info);
@@ -210,33 +214,11 @@
info.damageAccumulator->popTransform();
}
-class PushAnimatorsFunctor {
-public:
- PushAnimatorsFunctor(RenderNode* target, TreeInfo& info)
- : mTarget(target), mInfo(info) {}
-
- bool operator() (const sp<BaseRenderNodeAnimator>& animator) {
- animator->setupStartValueIfNecessary(mTarget, mInfo);
- return animator->isFinished();
- }
-private:
- RenderNode* mTarget;
- TreeInfo& mInfo;
-};
-
void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
// Push the animators first so that setupStartValueIfNecessary() is called
// before properties() is trampled by stagingProperties(), as they are
// required by some animators.
- if (mNeedsAnimatorsSync) {
- mAnimators.resize(mStagingAnimators.size());
- std::vector< sp<BaseRenderNodeAnimator> >::iterator it;
- PushAnimatorsFunctor functor(this, info);
- // hint: this means copy_if_not()
- it = std::remove_copy_if(mStagingAnimators.begin(), mStagingAnimators.end(),
- mAnimators.begin(), functor);
- mAnimators.resize(std::distance(mAnimators.begin(), it));
- }
+ mAnimatorManager.pushStaging(info);
if (mDirtyPropertyFields) {
mDirtyPropertyFields = 0;
damageSelf(info);
@@ -267,8 +249,7 @@
mNeedsDisplayListDataSync = false;
// Do a push pass on the old tree to handle freeing DisplayListData
// that are no longer used
- TreeInfo oldTreeInfo(TreeInfo::MODE_MAYBE_DETACHING, info.renderState);
- oldTreeInfo.damageAccumulator = info.damageAccumulator;
+ TreeInfo oldTreeInfo(TreeInfo::MODE_MAYBE_DETACHING, info);
prepareSubTree(oldTreeInfo, mDisplayListData);
delete mDisplayListData;
mDisplayListData = mStagingDisplayListData;
@@ -277,39 +258,6 @@
}
}
-class AnimateFunctor {
-public:
- AnimateFunctor(RenderNode* target, TreeInfo& info)
- : mTarget(target), mInfo(info) {}
-
- bool operator() (const sp<BaseRenderNodeAnimator>& animator) {
- return animator->animate(mTarget, mInfo);
- }
-private:
- RenderNode* mTarget;
- TreeInfo& mInfo;
-};
-
-void RenderNode::evaluateAnimations(TreeInfo& info) {
- if (!mAnimators.size()) return;
-
- // TODO: Can we target this better? For now treat it like any other staging
- // property push and just damage self before and after animators are run
-
- damageSelf(info);
- info.damageAccumulator->popTransform();
-
- AnimateFunctor functor(this, info);
- std::vector< sp<BaseRenderNodeAnimator> >::iterator newEnd;
- newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
- mAnimators.erase(newEnd, mAnimators.end());
- mProperties.updateMatrix();
- info.out.hasAnimations |= mAnimators.size();
-
- info.damageAccumulator->pushTransform(this);
- damageSelf(info);
-}
-
void RenderNode::prepareSubTree(TreeInfo& info, DisplayListData* subtree) {
if (subtree) {
TextureCache& cache = Caches::getInstance().textureCache;
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 3980dad..7d42b59 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -20,18 +20,11 @@
#define LOG_TAG "OpenGLRenderer"
#endif
-#include <set>
-#include <vector>
-
#include <SkCamera.h>
#include <SkMatrix.h>
-#include <private/hwui/DrawGlInfo.h>
-
-#include <utils/KeyedVector.h>
#include <utils/LinearAllocator.h>
#include <utils/RefBase.h>
-#include <utils/SortedVector.h>
#include <utils/String8.h>
#include <utils/Vector.h>
@@ -39,6 +32,7 @@
#include <androidfw/ResourceTypes.h>
+#include "AnimatorManager.h"
#include "DamageAccumulator.h"
#include "Debug.h"
#include "Matrix.h"
@@ -176,19 +170,7 @@
ANDROID_API virtual void prepareTree(TreeInfo& info);
// UI thread only!
- ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
- animator->onAttached(this);
- mStagingAnimators.insert(animator);
- mNeedsAnimatorsSync = true;
- }
-
- // UI thread only!
- ANDROID_API void removeAnimator(const sp<BaseRenderNodeAnimator>& animator) {
- mStagingAnimators.erase(animator);
- // Force a sync of the staging property value
- mDirtyPropertyFields |= animator->dirtyMask();
- mNeedsAnimatorsSync = true;
- }
+ ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
protected:
virtual void damageSelf(TreeInfo& info);
@@ -262,7 +244,6 @@
void prepareTreeImpl(TreeInfo& info);
void pushStagingPropertiesChanges(TreeInfo& info);
void pushStagingDisplayListChanges(TreeInfo& info);
- void evaluateAnimations(TreeInfo& info);
void prepareSubTree(TreeInfo& info, DisplayListData* subtree);
void applyLayerPropertiesToLayer(TreeInfo& info);
void prepareLayer(TreeInfo& info);
@@ -278,9 +259,8 @@
DisplayListData* mDisplayListData;
DisplayListData* mStagingDisplayListData;
- bool mNeedsAnimatorsSync;
- std::set< sp<BaseRenderNodeAnimator> > mStagingAnimators;
- std::vector< sp<BaseRenderNodeAnimator> > mAnimators;
+ friend class AnimatorManager;
+ AnimatorManager mAnimatorManager;
// Owned by RT. Lifecycle is managed by prepareTree(), with the exception
// being in ~RenderNode() which may happen on any thread.
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 249e525..083100e 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -79,6 +79,17 @@
, errorHandler(NULL)
{}
+ explicit TreeInfo(TraversalMode mode, const TreeInfo& clone)
+ : mode(mode)
+ , frameTimeMs(clone.frameTimeMs)
+ , animationHook(clone.animationHook)
+ , prepareTextures(mode == MODE_FULL)
+ , damageAccumulator(clone.damageAccumulator)
+ , renderState(clone.renderState)
+ , renderer(clone.renderer)
+ , errorHandler(clone.errorHandler)
+ {}
+
const TraversalMode mode;
nsecs_t frameTimeMs;
AnimationHook* animationHook;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index fe4edf8..9c3cf44 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -123,8 +123,8 @@
mHaveNewSurface |= mEglManager.makeCurrent(mEglSurface);
}
-void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater, TreeInfo& info) {
- bool success = layerUpdater->apply(info);
+void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
+ bool success = layerUpdater->apply();
LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!");
if (layerUpdater->backingLayer()->deferredUpdateScheduled) {
mCanvas->pushLayerUpdate(layerUpdater->backingLayer());
@@ -148,9 +148,7 @@
info.out.canDrawThisFrame = !runningBehind;
if (info.out.hasAnimations || !info.out.canDrawThisFrame) {
- if (info.out.hasFunctors) {
- info.out.requiresUiRedraw = true;
- } else if (!info.out.requiresUiRedraw) {
+ if (!info.out.requiresUiRedraw) {
// If animationsNeedsRedraw is set don't bother posting for an RT anim
// as we will just end up fighting the UI thread.
mRenderThread.postFrameCallback(this);
@@ -239,8 +237,7 @@
bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
requireGlContext();
- TreeInfo info(TreeInfo::MODE_FULL, mRenderThread.renderState());
- layer->apply(info);
+ layer->apply();
return LayerRenderer::copyLayer(mRenderThread.renderState(), layer->backingLayer(), bitmap);
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index d2ce1a6..dbfb3d2 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -56,7 +56,7 @@
void setup(int width, int height, const Vector3& lightCenter, float lightRadius);
void setOpaque(bool opaque);
void makeCurrent();
- void processLayerUpdate(DeferredLayerUpdater* layerUpdater, TreeInfo& info);
+ void processLayerUpdate(DeferredLayerUpdater* layerUpdater);
void prepareTree(TreeInfo& info);
void draw();
void destroyCanvasAndSurface();
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index fddffd5..dd34e09 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -127,7 +127,7 @@
Caches::getInstance().textureCache.resetMarkInUse();
for (size_t i = 0; i < mLayers.size(); i++) {
- mContext->processLayerUpdate(mLayers[i].get(), info);
+ mContext->processLayerUpdate(mLayers[i].get());
}
mLayers.clear();
mContext->prepareTree(info);
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 96f0add..243cc5d 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -41,7 +41,7 @@
enum SyncResult {
kSync_OK = 0,
- kSync_UIRedrawRequired = 1 << 1,
+ kSync_UIRedrawRequired = 1 << 0,
};
/*
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 394e437..57c66da 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -25,6 +25,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Set;
/**
@@ -154,6 +155,7 @@
private int mContentType = CONTENT_TYPE_UNKNOWN;
private int mFlags = 0x0;
private HashSet<String> mTags;
+ private String mFormattedTags;
private AudioAttributes() {
}
@@ -241,6 +243,12 @@
aa.mUsage = mUsage;
aa.mFlags = mFlags;
aa.mTags = (HashSet<String>) mTags.clone();
+ final Iterator<String> tagIterator = mTags.iterator();
+ String allTagsInOne = new String();
+ while (tagIterator.hasNext()) {
+ allTagsInOne += tagIterator.next() + ";";
+ }
+ aa.mFormattedTags = allTagsInOne;
return aa;
}
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index bd2be1b..a471e83 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -227,6 +227,22 @@
private int mChannelMask;
private int mPropertySetMask;
+ int getEncoding() {
+ return mEncoding;
+ }
+
+ int getSampleRate() {
+ return mSampleRate;
+ }
+
+ int getChannelMask() {
+ return mChannelMask;
+ }
+
+ int getPropertySetMask() {
+ return mPropertySetMask;
+ }
+
/**
* @hide CANDIDATE FOR PUBLIC API
* Builder class for {@link AudioFormat} objects.
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index fb19242..c8b51e0 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2811,38 +2811,6 @@
* Only useful for volume controllers.
* @hide
*/
- public int getRemoteStreamVolume() {
- // TODO STOPSHIP switch callers to use media sessions instead
- Log.e(TAG, "Need to implement new Remote Volume!");
- return 0;
- }
-
- /**
- * Only useful for volume controllers.
- * @hide
- */
- public int getRemoteStreamMaxVolume() {
- // TODO STOPSHIP switch callers to use media sessions instead
- Log.e(TAG, "Need to implement new Remote Volume!");
- return 0;
- }
-
- /**
- * Only useful for volume controllers.
- * @hide
- */
- public void setRemoteStreamVolume(int index) {
- try {
- getService().setRemoteStreamVolume(index);
- } catch (RemoteException e) {
- Log.w(TAG, "Error setting remote stream volume", e);
- }
- }
-
- /**
- * Only useful for volume controllers.
- * @hide
- */
public boolean isStreamAffectedByRingerMode(int streamType) {
try {
return getService().isStreamAffectedByRingerMode(streamType);
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 0c224a6..d882bc8 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -85,6 +85,7 @@
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.Set;
/**
@@ -443,8 +444,7 @@
// Devices for which the volume is fixed and VolumePanel slider should be disabled
final int mFixedVolumeDevices = AudioSystem.DEVICE_OUT_HDMI |
AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET |
- AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET |
- AudioSystem.DEVICE_OUT_ALL_USB;
+ AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET;
// TODO merge orientation and rotation
private final boolean mMonitorOrientation;
@@ -849,10 +849,8 @@
}
if (streamType == STREAM_REMOTE_MUSIC) {
- // don't play sounds for remote
- flags &= ~(AudioManager.FLAG_PLAY_SOUND|AudioManager.FLAG_FIXED_VOLUME);
- //if (DEBUG_VOL) Log.i(TAG, "Need to adjust remote volume: calling adjustRemoteVolume()");
- mMediaFocusControl.adjustRemoteVolume(AudioSystem.STREAM_MUSIC, direction, flags);
+ // TODO bounce it to MediaSessionService to find an appropriate
+ // session
} else {
adjustStreamVolume(streamType, direction, flags, callingPackage);
}
@@ -4794,4 +4792,91 @@
}
mVolumeController.setController(controller);
}
+
+ public static class VolumeController {
+ private static final String TAG = "VolumeController";
+
+ private IVolumeController mController;
+
+ public void setController(IVolumeController controller) {
+ mController = controller;
+ }
+
+ public boolean isSameBinder(IVolumeController controller) {
+ return Objects.equals(asBinder(), binder(controller));
+ }
+
+ public IBinder asBinder() {
+ return binder(mController);
+ }
+
+ private static IBinder binder(IVolumeController controller) {
+ return controller == null ? null : controller.asBinder();
+ }
+
+ @Override
+ public String toString() {
+ return "VolumeController(" + asBinder() + ")";
+ }
+
+ public void postDisplaySafeVolumeWarning(int flags) {
+ if (mController == null)
+ return;
+ try {
+ mController.displaySafeVolumeWarning(flags);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling displaySafeVolumeWarning", e);
+ }
+ }
+
+ public void postVolumeChanged(int streamType, int flags) {
+ if (mController == null)
+ return;
+ try {
+ mController.volumeChanged(streamType, flags);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling volumeChanged", e);
+ }
+ }
+
+ public void postMasterVolumeChanged(int flags) {
+ if (mController == null)
+ return;
+ try {
+ mController.masterVolumeChanged(flags);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling masterVolumeChanged", e);
+ }
+ }
+
+ public void postMasterMuteChanged(int flags) {
+ if (mController == null)
+ return;
+ try {
+ mController.masterMuteChanged(flags);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling masterMuteChanged", e);
+ }
+ }
+
+ public void setLayoutDirection(int layoutDirection) {
+ if (mController == null)
+ return;
+ try {
+ mController.setLayoutDirection(layoutDirection);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling setLayoutDirection", e);
+ }
+ }
+
+ public void postDismiss() {
+ if (mController == null)
+ return;
+ try {
+ mController.dismiss();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling dismiss", e);
+ }
+ }
+ }
}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 63ed10c..d3e0f48 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -28,11 +28,13 @@
*/
public class AudioSystem
{
- /* These values must be kept in sync with AudioSystem.h */
+ /* These values must be kept in sync with system/audio.h */
/*
* If these are modified, please also update Settings.System.VOLUME_SETTINGS
* and attrs.xml and AudioManager.java.
*/
+ /* The default audio stream */
+ public static final int STREAM_DEFAULT = -1;
/* The audio stream for phone calls */
public static final int STREAM_VOICE_CALL = 0;
/* The audio stream for system sounds */
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 3a72833..3f79e9a 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -21,6 +21,8 @@
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.NioUtils;
+import java.util.Iterator;
+import java.util.Set;
import android.annotation.IntDef;
import android.app.ActivityThread;
@@ -233,6 +235,8 @@
* {@link AudioManager#STREAM_DTMF}.
*/
private int mStreamType = AudioManager.STREAM_MUSIC;
+
+ private final AudioAttributes mAttributes;
/**
* The way audio is consumed by the audio sink, streaming or static.
*/
@@ -349,21 +353,69 @@
int bufferSizeInBytes, int mode, int sessionId)
throws IllegalArgumentException {
// mState already == STATE_UNINITIALIZED
+ this((new AudioAttributes.Builder())
+ .setLegacyStreamType(streamType)
+ .build(),
+ (new AudioFormat.Builder())
+ .setChannelMask(channelConfig)
+ .setEncoding(audioFormat)
+ .setSampleRate(sampleRateInHz)
+ .build(),
+ bufferSizeInBytes,
+ mode, sessionId);
+ }
+
+ /**
+ * @hide
+ * CANDIDATE FOR PUBLIC API
+ * Constructor with AudioAttributes and AudioFormat
+ * @param aa
+ * @param format
+ * @param bufferSizeInBytes
+ * @param mode
+ * @param sessionId
+ */
+ public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,
+ int mode, int sessionId)
+ throws IllegalArgumentException {
+ // mState already == STATE_UNINITIALIZED
// remember which looper is associated with the AudioTrack instantiation
Looper looper;
if ((looper = Looper.myLooper()) == null) {
looper = Looper.getMainLooper();
}
- mInitializationLooper = looper;
- audioParamCheck(streamType, sampleRateInHz, channelConfig, audioFormat, mode);
+ int rate = 0;
+ if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE) != 0)
+ {
+ rate = format.getSampleRate();
+ } else {
+ rate = AudioSystem.getPrimaryOutputSamplingRate();
+ if (rate <= 0) {
+ rate = 44100;
+ }
+ }
+ int channelMask = AudioFormat.CHANNEL_OUT_FRONT_LEFT | AudioFormat.CHANNEL_OUT_FRONT_RIGHT;
+ if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK) != 0)
+ {
+ channelMask = format.getChannelMask();
+ }
+ int encoding = AudioFormat.ENCODING_DEFAULT;
+ if ((format.getPropertySetMask() & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_ENCODING) != 0) {
+ encoding = format.getEncoding();
+ }
+ audioParamCheck(rate, channelMask, encoding, mode);
+ mStreamType = AudioSystem.STREAM_DEFAULT;
audioBuffSizeCheck(bufferSizeInBytes);
+ mInitializationLooper = looper;
IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
mAppOps = IAppOpsService.Stub.asInterface(b);
+ mAttributes = (new AudioAttributes.Builder(attributes).build());
+
if (sessionId < 0) {
throw new IllegalArgumentException("Invalid audio session ID: "+sessionId);
}
@@ -371,8 +423,8 @@
int[] session = new int[1];
session[0] = sessionId;
// native initialization
- int initResult = native_setup(new WeakReference<AudioTrack>(this),
- mStreamType, mSampleRate, mChannels, mAudioFormat,
+ int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
+ mSampleRate, mChannels, mAudioFormat,
mNativeBufferSizeInBytes, mDataLoadMode, session);
if (initResult != SUCCESS) {
loge("Error code "+initResult+" when initializing AudioTrack.");
@@ -401,27 +453,13 @@
// Convenience method for the constructor's parameter checks.
// This is where constructor IllegalArgumentException-s are thrown
// postconditions:
- // mStreamType is valid
// mChannelCount is valid
// mChannels is valid
// mAudioFormat is valid
// mSampleRate is valid
// mDataLoadMode is valid
- private void audioParamCheck(int streamType, int sampleRateInHz,
+ private void audioParamCheck(int sampleRateInHz,
int channelConfig, int audioFormat, int mode) {
-
- //--------------
- // stream type
- if( (streamType != AudioManager.STREAM_ALARM) && (streamType != AudioManager.STREAM_MUSIC)
- && (streamType != AudioManager.STREAM_RING) && (streamType != AudioManager.STREAM_SYSTEM)
- && (streamType != AudioManager.STREAM_VOICE_CALL)
- && (streamType != AudioManager.STREAM_NOTIFICATION)
- && (streamType != AudioManager.STREAM_BLUETOOTH_SCO)
- && (streamType != AudioManager.STREAM_DTMF)) {
- throw new IllegalArgumentException("Invalid stream type.");
- }
- mStreamType = streamType;
-
//--------------
// sample rate, note these values are subject to change
if ( (sampleRateInHz < 4000) || (sampleRateInHz > 48000) ) {
@@ -1559,8 +1597,12 @@
// Native methods called from the Java side
//--------------------
- private native final int native_setup(Object audiotrack_this,
- int streamType, int sampleRate, int channelMask, int audioFormat,
+ // post-condition: mStreamType is overwritten with a value
+ // that reflects the audio attributes (e.g. an AudioAttributes object with a usage of
+ // AudioAttributes.USAGE_MEDIA will map to AudioManager.STREAM_MUSIC
+ private native final int native_setup(Object /*WeakReference<AudioTrack>*/ audiotrack_this,
+ Object /*AudioAttributes*/ attributes,
+ int sampleRate, int channelMask, int audioFormat,
int buffSizeInBytes, int mode, int[] sessionId);
private native final void native_finalize();
diff --git a/media/java/android/media/IRemoteVolumeController.aidl b/media/java/android/media/IRemoteVolumeController.aidl
new file mode 100644
index 0000000..e4a4a42
--- /dev/null
+++ b/media/java/android/media/IRemoteVolumeController.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.media;
+
+import android.media.session.ISessionController;
+
+/**
+ * AIDL for the MediaSessionService to report interesting events on remote playback
+ * to a volume control dialog. See also IVolumeController for the AudioService half.
+ * TODO add in better support for multiple remote sessions.
+ * @hide
+ */
+oneway interface IRemoteVolumeController {
+ void remoteVolumeChanged(ISessionController session, int flags);
+ // sets the default session to use with the slider, replaces remoteSliderVisibility
+ // on IVolumeController
+ void updateRemoteController(ISessionController session);
+}
diff --git a/media/java/android/media/IVolumeController.aidl b/media/java/android/media/IVolumeController.aidl
index 35d7708..e3593a6 100644
--- a/media/java/android/media/IVolumeController.aidl
+++ b/media/java/android/media/IVolumeController.aidl
@@ -16,17 +16,12 @@
package android.media;
-
/**
- * AIDL for the AudioService to report interesting events to a remote volume control dialog.
+ * AIDL for the AudioService to report interesting events to a volume control
+ * dialog in another process.
* @hide
*/
oneway interface IVolumeController {
- void hasNewRemotePlaybackInfo();
-
- void remoteVolumeChanged(int streamType, int flags);
-
- void remoteSliderVisibility(boolean visible);
void displaySafeVolumeWarning(int flags);
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 6559bc5..25f10d2 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -325,7 +325,8 @@
* @throws NotProvisionedException if provisioning is needed
* @throws ResourceBusyException if required resources are in use
*/
- public native byte[] openSession() throws NotProvisionedException;
+ public native byte[] openSession() throws NotProvisionedException,
+ ResourceBusyException;
/**
* Close a session on the MediaDrm object that was previously opened
diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java
index a4a7c4e..6004ecf 100644
--- a/media/java/android/media/MediaFocusControl.java
+++ b/media/java/android/media/MediaFocusControl.java
@@ -77,7 +77,7 @@
private final MediaEventHandler mEventHandler;
private final Context mContext;
private final ContentResolver mContentResolver;
- private final VolumeController mVolumeController;
+ private final AudioService.VolumeController mVolumeController;
private final BroadcastReceiver mReceiver = new PackageIntentsReceiver();
private final AppOpsManager mAppOps;
private final KeyguardManager mKeyguardManager;
@@ -85,7 +85,7 @@
private final NotificationListenerObserver mNotifListenerObserver;
protected MediaFocusControl(Looper looper, Context cntxt,
- VolumeController volumeCtrl, AudioService as) {
+ AudioService.VolumeController volumeCtrl, AudioService as) {
mEventHandler = new MediaEventHandler(looper);
mContext = cntxt;
mContentResolver = mContext.getContentResolver();
@@ -2061,29 +2061,6 @@
}
}
- protected void adjustRemoteVolume(int streamType, int direction, int flags) {
- int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
- boolean volFixed = false;
- synchronized (mMainRemote) {
- if (!mMainRemoteIsActive) {
- if (DEBUG_VOL) Log.w(TAG, "adjustRemoteVolume didn't find an active client");
- return;
- }
- rccId = mMainRemote.mRccId;
- volFixed = (mMainRemote.mVolumeHandling ==
- RemoteControlClient.PLAYBACK_VOLUME_FIXED);
- }
- // unlike "local" stream volumes, we can't compute the new volume based on the direction,
- // we can only notify the remote that volume needs to be updated, and we'll get an async'
- // update through setPlaybackInfoForRcc()
- if (!volFixed) {
- sendVolumeUpdateToRemote(rccId, direction);
- }
-
- // fire up the UI
- mVolumeController.postRemoteVolumeChanged(streamType, flags);
- }
-
private void sendVolumeUpdateToRemote(int rccId, int direction) {
if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); }
if (direction == 0) {
@@ -2183,27 +2160,9 @@
}
private void onReevaluateRemote() {
- if (DEBUG_VOL) { Log.w(TAG, "onReevaluateRemote()"); }
- // is there a registered RemoteControlClient that is handling remote playback
- boolean hasRemotePlayback = false;
- synchronized (mPRStack) {
- // iteration stops when PLAYBACK_TYPE_REMOTE is found, so remote control stack
- // traversal order doesn't matter
- Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- PlayerRecord prse = stackIterator.next();
- if (prse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) {
- hasRemotePlayback = true;
- break;
- }
- }
- }
- synchronized (mMainRemote) {
- if (mHasRemotePlayback != hasRemotePlayback) {
- mHasRemotePlayback = hasRemotePlayback;
- mVolumeController.postRemoteSliderVisibility(hasRemotePlayback);
- }
- }
+ // TODO This was used to notify VolumePanel if there was remote playback
+ // in the stack. This is now in MediaSessionService. More code should be
+ // removed.
}
}
diff --git a/media/java/android/media/MediaMetadata.java b/media/java/android/media/MediaMetadata.java
index 5dc8e1b..5d2f3bd 100644
--- a/media/java/android/media/MediaMetadata.java
+++ b/media/java/android/media/MediaMetadata.java
@@ -260,7 +260,7 @@
rating = mBundle.getParcelable(key);
} catch (Exception e) {
// ignore, value was not a bitmap
- Log.d(TAG, "Failed to retrieve a key as Rating.", e);
+ Log.w(TAG, "Failed to retrieve a key as Rating.", e);
}
return rating;
}
@@ -278,7 +278,7 @@
bmp = mBundle.getParcelable(key);
} catch (Exception e) {
// ignore, value was not a bitmap
- Log.d(TAG, "Failed to retrieve a key as Bitmap.", e);
+ Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
}
return bmp;
}
@@ -323,8 +323,8 @@
return EDITOR_KEY_MAPPING.get(editorKey, null);
}
- public static final Parcelable.Creator<MediaMetadata> CREATOR
- = new Parcelable.Creator<MediaMetadata>() {
+ public static final Parcelable.Creator<MediaMetadata> CREATOR =
+ new Parcelable.Creator<MediaMetadata>() {
@Override
public MediaMetadata createFromParcel(Parcel in) {
return new MediaMetadata(in);
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 66175d0..d1909bc 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -2335,7 +2335,9 @@
case MEDIA_INFO_EXTERNAL_METADATA_UPDATE:
msg.arg1 = MEDIA_INFO_METADATA_UPDATE;
// update default track selection
- mSubtitleController.selectDefaultTrack();
+ if (mSubtitleController != null) {
+ mSubtitleController.selectDefaultTrack();
+ }
break;
}
diff --git a/media/java/android/media/Rating.java b/media/java/android/media/Rating.java
index f4fbe2c..a518bb4 100644
--- a/media/java/android/media/Rating.java
+++ b/media/java/android/media/Rating.java
@@ -30,6 +30,7 @@
*/
public final class Rating implements Parcelable {
private final static String TAG = "Rating";
+
/**
* Indicates a rating style is not supported. A Rating will never have this
* type, but can be used by other classes to indicate they do not support
@@ -79,12 +80,8 @@
mRatingValue = rating;
}
-
- /**
- * @hide
- */
@Override
- public String toString () {
+ public String toString() {
return "Rating:style=" + mRatingStyle + " rating="
+ (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue));
}
@@ -107,9 +104,12 @@
* @param p Parcel object to read the Rating from
* @return a new Rating created from the data in the parcel
*/
+ @Override
public Rating createFromParcel(Parcel p) {
return new Rating(p.readInt(), p.readFloat());
}
+
+ @Override
public Rating[] newArray(int size) {
return new Rating[size];
}
diff --git a/media/java/android/media/VolumeController.java b/media/java/android/media/VolumeController.java
deleted file mode 100644
index 6b70cc3..0000000
--- a/media/java/android/media/VolumeController.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.Objects;
-
-/**
- * Wraps the remote volume controller interface as a convenience to audio service.
- * @hide
- */
-public class VolumeController {
- private static final String TAG = "VolumeController";
-
- private IVolumeController mController;
-
- public void setController(IVolumeController controller) {
- mController = controller;
- }
-
- public boolean isSameBinder(IVolumeController controller) {
- return Objects.equals(asBinder(), binder(controller));
- }
-
- public IBinder asBinder() {
- return binder(mController);
- }
-
- private static IBinder binder(IVolumeController controller) {
- return controller == null ? null : controller.asBinder();
- }
-
- @Override
- public String toString() {
- return "VolumeController(" + asBinder() + ")";
- }
-
- public void postHasNewRemotePlaybackInfo() {
- if (mController == null) return;
- try {
- mController.hasNewRemotePlaybackInfo();
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling hasNewRemotePlaybackInfo", e);
- }
- }
-
- public void postRemoteVolumeChanged(int streamType, int flags) {
- if (mController == null) return;
- try {
- mController.remoteVolumeChanged(streamType, flags);
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling remoteVolumeChanged", e);
- }
- }
-
- public void postRemoteSliderVisibility(boolean visible) {
- if (mController == null) return;
- try {
- mController.remoteSliderVisibility(visible);
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling remoteSliderVisibility", e);
- }
- }
-
- public void postDisplaySafeVolumeWarning(int flags) {
- if (mController == null) return;
- try {
- mController.displaySafeVolumeWarning(flags);
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling displaySafeVolumeWarning", e);
- }
- }
-
- public void postVolumeChanged(int streamType, int flags) {
- if (mController == null) return;
- try {
- mController.volumeChanged(streamType, flags);
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling volumeChanged", e);
- }
- }
-
- public void postMasterVolumeChanged(int flags) {
- if (mController == null) return;
- try {
- mController.masterVolumeChanged(flags);
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling masterVolumeChanged", e);
- }
- }
-
- public void postMasterMuteChanged(int flags) {
- if (mController == null) return;
- try {
- mController.masterMuteChanged(flags);
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling masterMuteChanged", e);
- }
- }
-
- public void setLayoutDirection(int layoutDirection) {
- if (mController == null) return;
- try {
- mController.setLayoutDirection(layoutDirection);
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling setLayoutDirection", e);
- }
- }
-
- public void postDismiss() {
- if (mController == null) return;
- try {
- mController.dismiss();
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling dismiss", e);
- }
- }
-}
\ No newline at end of file
diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java
index 7d93b40..d151e66 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -16,8 +16,6 @@
package android.media;
import android.media.session.MediaSession;
-import android.os.RemoteException;
-import android.util.Log;
/**
* Handles requests to adjust or set the volume on a session. This is also used
@@ -26,8 +24,6 @@
* {@link MediaSession#setPlaybackToRemote}.
*/
public abstract class VolumeProvider {
- private static final String TAG = "VolumeProvider";
-
/**
* The volume is fixed and can not be modified. Requests to change volume
* should be ignored.
@@ -50,8 +46,7 @@
private final int mControlType;
private final int mMaxVolume;
-
- private MediaSession mSession;
+ private Callback mCallback;
/**
* Create a new volume provider for handling volume events. You must specify
@@ -92,10 +87,12 @@
}
/**
- * Notify the system that the remote playback's volume has been changed.
+ * Notify the system that the volume has been changed.
*/
public final void notifyVolumeChanged() {
- mSession.notifyRemoteVolumeChanged(this);
+ if (mCallback != null) {
+ mCallback.onVolumeChanged(this);
+ }
}
/**
@@ -116,9 +113,18 @@
}
/**
+ * Sets a callback to receive volume changes.
* @hide
*/
- public void setSession(MediaSession session) {
- mSession = session;
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ /**
+ * Listens for changes to the volume.
+ * @hide
+ */
+ public static abstract class Callback {
+ public abstract void onVolumeChanged(VolumeProvider volumeProvider);
}
}
\ No newline at end of file
diff --git a/media/java/android/media/session/ISessionControllerCallback.aidl b/media/java/android/media/session/ISessionControllerCallback.aidl
index e823153..baa1379 100644
--- a/media/java/android/media/session/ISessionControllerCallback.aidl
+++ b/media/java/android/media/session/ISessionControllerCallback.aidl
@@ -17,6 +17,7 @@
import android.media.MediaMetadata;
import android.media.session.RouteInfo;
+import android.media.session.ParcelableVolumeInfo;
import android.media.session.PlaybackState;
import android.os.Bundle;
@@ -30,4 +31,5 @@
// These callbacks are for the TransportController
void onPlaybackStateChanged(in PlaybackState state);
void onMetadataChanged(in MediaMetadata metadata);
+ void onVolumeInfoChanged(in ParcelableVolumeInfo info);
}
\ No newline at end of file
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index bd1fa85..dce84d4 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -16,6 +16,7 @@
package android.media.session;
import android.content.ComponentName;
+import android.media.IRemoteVolumeController;
import android.media.session.IActiveSessionsListener;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
@@ -34,4 +35,7 @@
void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName,
int userId);
void removeSessionsListener(in IActiveSessionsListener listener);
+
+ // This is for the system volume UI only
+ void setRemoteVolumeController(in IRemoteVolumeController rvc);
}
\ No newline at end of file
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 84dad25..7653e5a 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -16,6 +16,9 @@
package android.media.session;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
@@ -44,12 +47,13 @@
* MediaController objects are thread-safe.
*/
public final class MediaController {
- private static final String TAG = "SessionController";
+ private static final String TAG = "MediaController";
private static final int MSG_EVENT = 1;
private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
private static final int MSG_UPDATE_METADATA = 3;
private static final int MSG_ROUTE = 4;
+ private static final int MSG_UPDATE_VOLUME = 5;
private final ISessionController mSessionBinder;
@@ -75,14 +79,14 @@
}
/**
- * Get a new MediaController for a MediaSessionToken. If successful the
- * controller returned will be connected to the session that generated the
- * token.
+ * Get a new media controller from a session token which may have
+ * been obtained from another process. If successful the controller returned
+ * will be connected to the session that generated the token.
*
- * @param token The session token to use
- * @return A controller for the session or null
+ * @param token The session token to control.
+ * @return A controller for the session or null if inaccessible.
*/
- public static MediaController fromToken(MediaSessionToken token) {
+ public static MediaController fromToken(@NonNull MediaSessionToken token) {
return fromBinder(token.getBinder());
}
@@ -91,7 +95,7 @@
*
* @return A controls instance
*/
- public TransportControls getTransportControls() {
+ public @NonNull TransportControls getTransportControls() {
return mTransportController;
}
@@ -102,7 +106,7 @@
* @param keyEvent The media button event to dispatch.
* @return true if the event was sent to the session, false otherwise.
*/
- public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) {
+ public boolean dispatchMediaButtonEvent(@NonNull KeyEvent keyEvent) {
if (keyEvent == null) {
throw new IllegalArgumentException("KeyEvent may not be null");
}
@@ -122,7 +126,7 @@
*
* @return The current PlaybackState or null
*/
- public PlaybackState getPlaybackState() {
+ public @Nullable PlaybackState getPlaybackState() {
try {
return mSessionBinder.getPlaybackState();
} catch (RemoteException e) {
@@ -136,7 +140,7 @@
*
* @return The current MediaMetadata or null.
*/
- public MediaMetadata getMetadata() {
+ public @Nullable MediaMetadata getMetadata() {
try {
return mSessionBinder.getMetadata();
} catch (RemoteException e) {
@@ -188,7 +192,7 @@
*
* @return The current volume info or null.
*/
- public VolumeInfo getVolumeInfo() {
+ public @Nullable VolumeInfo getVolumeInfo() {
try {
ParcelableVolumeInfo result = mSessionBinder.getVolumeAttributes();
return new VolumeInfo(result.volumeType, result.audioStream, result.controlType,
@@ -201,29 +205,69 @@
}
/**
+ * Set the volume of the stream or output this session is playing on. The
+ * command will be ignored if it does not support
+ * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. The flags in
+ * {@link AudioManager} may be used to affect the handling.
+ *
+ * @see #getVolumeInfo()
+ * @param value The value to set it to, between 0 and the reported max.
+ * @param flags Any flags to pass with the command.
+ */
+ public void setVolumeTo(int value, int flags) {
+ try {
+ mSessionBinder.setVolumeTo(value, flags);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling setVolumeTo.", e);
+ }
+ }
+
+ /**
+ * Adjust the volume of the stream or output this session is playing on.
+ * Negative values will lower the volume. The command will be ignored if it
+ * does not support {@link VolumeProvider#VOLUME_CONTROL_RELATIVE} or
+ * {@link VolumeProvider#VOLUME_CONTROL_ABSOLUTE}. The flags in
+ * {@link AudioManager} may be used to affect the handling.
+ *
+ * @see #getVolumeInfo()
+ * @param delta The number of steps to adjust the volume by.
+ * @param flags Any flags to pass with the command.
+ */
+ public void adjustVolumeBy(int delta, int flags) {
+ try {
+ mSessionBinder.adjustVolumeBy(delta, flags);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error calling adjustVolumeBy.", e);
+ }
+ }
+
+ /**
* Adds a callback to receive updates from the Session. Updates will be
* posted on the caller's thread.
*
- * @param cb The callback object, must not be null
+ * @param callback The callback object, must not be null.
*/
- public void addCallback(Callback cb) {
- addCallback(cb, null);
+ public void addCallback(@NonNull Callback callback) {
+ addCallback(callback, null);
}
/**
* Adds a callback to receive updates from the session. Updates will be
* posted on the specified handler's thread.
*
- * @param cb Cannot be null.
+ * @param callback The callback object, must not be null.
* @param handler The handler to post updates on. If null the callers thread
- * will be used
+ * will be used.
*/
- public void addCallback(Callback cb, Handler handler) {
+ public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
if (handler == null) {
handler = new Handler();
}
synchronized (mLock) {
- addCallbackLocked(cb, handler);
+ addCallbackLocked(callback, handler);
}
}
@@ -231,11 +275,14 @@
* Stop receiving updates on the specified callback. If an update has
* already been posted you may still receive it after calling this method.
*
- * @param cb The callback to remove
+ * @param callback The callback to remove.
*/
- public void removeCallback(Callback cb) {
+ public void removeCallback(@NonNull Callback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null");
+ }
synchronized (mLock) {
- removeCallbackLocked(cb);
+ removeCallbackLocked(callback);
}
}
@@ -248,7 +295,8 @@
* @param params Any parameters to include with the command
* @param cb The callback to receive the result on
*/
- public void sendControlCommand(String command, Bundle params, ResultReceiver cb) {
+ public void sendControlCommand(@NonNull String command, @Nullable Bundle params,
+ @Nullable ResultReceiver cb) {
if (TextUtils.isEmpty(command)) {
throw new IllegalArgumentException("command cannot be null or empty");
}
@@ -298,12 +346,6 @@
}
private void addCallbackLocked(Callback cb, Handler handler) {
- if (cb == null) {
- throw new IllegalArgumentException("Callback cannot be null");
- }
- if (handler == null) {
- throw new IllegalArgumentException("Handler cannot be null");
- }
if (getHandlerForCallbackLocked(cb) != null) {
Log.w(TAG, "Callback is already added, ignoring");
return;
@@ -322,9 +364,6 @@
}
private boolean removeCallbackLocked(Callback cb) {
- if (cb == null) {
- throw new IllegalArgumentException("Callback cannot be null");
- }
boolean success = false;
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
MessageHandler handler = mCallbacks.get(i);
@@ -375,9 +414,10 @@
* specified interface. Controllers should only handle these for
* sessions they own.
*
- * @param event
+ * @param event The event from the session.
+ * @param extras Optional parameters for the event, may be null.
*/
- public void onSessionEvent(String event, Bundle extras) {
+ public void onSessionEvent(@NonNull String event, @Nullable Bundle extras) {
}
/**
@@ -394,16 +434,24 @@
*
* @param state The new playback state of the session
*/
- public void onPlaybackStateChanged(PlaybackState state) {
+ public void onPlaybackStateChanged(@NonNull PlaybackState state) {
}
/**
* Override to handle changes to the current metadata.
*
+ * @param metadata The current metadata for the session or null if none.
* @see MediaMetadata
- * @param metadata The current metadata for the session or null
*/
- public void onMetadataChanged(MediaMetadata metadata) {
+ public void onMetadataChanged(@Nullable MediaMetadata metadata) {
+ }
+
+ /**
+ * Override to handle changes to the volume info.
+ *
+ * @param info The current volume info for this session.
+ */
+ public void onVolumeInfoChanged(VolumeInfo info) {
}
}
@@ -551,8 +599,8 @@
/**
* Get the type of volume handling, either local or remote. One of:
* <ul>
- * <li>{@link MediaSession#VOLUME_TYPE_LOCAL}</li>
- * <li>{@link MediaSession#VOLUME_TYPE_REMOTE}</li>
+ * <li>{@link MediaSession#PLAYBACK_TYPE_LOCAL}</li>
+ * <li>{@link MediaSession#PLAYBACK_TYPE_REMOTE}</li>
* </ul>
*
* @return The type of volume handling this session is using.
@@ -563,7 +611,7 @@
/**
* Get the stream this is currently controlling volume on. When the volume
- * type is {@link MediaSession#VOLUME_TYPE_REMOTE} this value does not
+ * type is {@link MediaSession#PLAYBACK_TYPE_REMOTE} this value does not
* have meaning and should be ignored.
*
* @return The stream this session is playing on.
@@ -645,6 +693,16 @@
}
}
+ @Override
+ public void onVolumeInfoChanged(ParcelableVolumeInfo pvi) {
+ MediaController controller = mController.get();
+ if (controller != null) {
+ VolumeInfo info = new VolumeInfo(pvi.volumeType, pvi.audioStream, pvi.controlType,
+ pvi.maxVolume, pvi.currentVolume);
+ controller.postMessage(MSG_UPDATE_VOLUME, info, null);
+ }
+ }
+
}
private final static class MessageHandler extends Handler {
@@ -670,6 +728,9 @@
case MSG_UPDATE_METADATA:
mCallback.onMetadataChanged((MediaMetadata) msg.obj);
break;
+ case MSG_UPDATE_VOLUME:
+ mCallback.onVolumeInfoChanged((VolumeInfo) msg.obj);
+ break;
}
}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 406b1c3..7637ec8 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -51,10 +51,9 @@
* for all playback, though multiple sessions can be created to provide finer
* grain controls of media.
* <p>
- * A MediaSession is created by calling
- * {@link MediaSessionManager#createSession(String)}. Once a session is created
- * the owner of the session may use {@link #getSessionToken()} to allow apps to
- * create a {@link MediaController} to interact with this session.
+ * Once a session is created the owner of the session may pass its
+ * {@link #getSessionToken() session token} to other processes to allow them to
+ * create a {@link MediaController} to interact with the session.
* <p>
* To receive commands, media keys, and other events a {@link Callback} must be
* set with {@link #addCallback(Callback)}. To receive transport control
@@ -64,10 +63,10 @@
* When an app is finished performing playback it must call {@link #release()}
* to clean up the session and notify any controllers.
* <p>
- * MediaSession objects are thread safe
+ * MediaSession objects are thread safe.
*/
public final class MediaSession {
- private static final String TAG = "Session";
+ private static final String TAG = "MediaSession";
/**
* Set this flag on the session to indicate that it can handle media button
@@ -77,9 +76,8 @@
/**
* Set this flag on the session to indicate that it handles transport
- * control commands through a {@link TransportControlsCallback}. The
- * callback can be retrieved by calling
- * {@link #addTransportControlsCallback}.
+ * control commands through a {@link TransportControlsCallback}.
+ * The callback can be retrieved by calling {@link #addTransportControlsCallback}.
*/
public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
@@ -129,12 +127,12 @@
/**
* The session uses local playback.
*/
- public static final int VOLUME_TYPE_LOCAL = 1;
+ public static final int PLAYBACK_TYPE_LOCAL = 1;
/**
* The session uses remote playback.
*/
- public static final int VOLUME_TYPE_REMOTE = 2;
+ public static final int PLAYBACK_TYPE_REMOTE = 2;
private final Object mLock = new Object();
@@ -153,7 +151,7 @@
private Route mRoute;
private VolumeProvider mVolumeProvider;
- private boolean mActive = false;;
+ private boolean mActive = false;
/**
* @hide
@@ -177,7 +175,7 @@
*
* @param callback The callback object
*/
- public void addCallback(Callback callback) {
+ public void addCallback(@NonNull Callback callback) {
addCallback(callback, null);
}
@@ -188,7 +186,7 @@
* @param callback The callback to receive updates on.
* @param handler The handler that events should be posted on.
*/
- public void addCallback(Callback callback, Handler handler) {
+ public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
if (callback == null) {
throw new IllegalArgumentException("Callback cannot be null");
}
@@ -211,7 +209,7 @@
*
* @param callback The callback to remove.
*/
- public void removeCallback(Callback callback) {
+ public void removeCallback(@NonNull Callback callback) {
synchronized (mLock) {
removeCallbackLocked(callback);
}
@@ -223,7 +221,7 @@
*
* @param pi The intent to launch to show UI for this Session.
*/
- public void setLaunchPendingIntent(PendingIntent pi) {
+ public void setLaunchPendingIntent(@Nullable PendingIntent pi) {
// TODO
}
@@ -234,7 +232,7 @@
* @param mbr The receiver component to send the media button event to.
* @hide
*/
- public void setMediaButtonReceiver(ComponentName mbr) {
+ public void setMediaButtonReceiver(@Nullable ComponentName mbr) {
try {
mBinder.setMediaButtonReceiver(mbr);
} catch (RemoteException e) {
@@ -267,7 +265,7 @@
*/
public void setPlaybackToLocal(int stream) {
try {
- mBinder.configureVolumeHandling(VOLUME_TYPE_LOCAL, stream, 0);
+ mBinder.configureVolumeHandling(PLAYBACK_TYPE_LOCAL, stream, 0);
} catch (RemoteException e) {
Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
}
@@ -283,15 +281,20 @@
* @param volumeProvider The provider that will handle volume changes. May
* not be null.
*/
- public void setPlaybackToRemote(VolumeProvider volumeProvider) {
+ public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) {
if (volumeProvider == null) {
throw new IllegalArgumentException("volumeProvider may not be null!");
}
mVolumeProvider = volumeProvider;
- volumeProvider.setSession(this);
+ volumeProvider.setCallback(new VolumeProvider.Callback() {
+ @Override
+ public void onVolumeChanged(VolumeProvider volumeProvider) {
+ notifyRemoteVolumeChanged(volumeProvider);
+ }
+ });
try {
- mBinder.configureVolumeHandling(VOLUME_TYPE_REMOTE, volumeProvider.getVolumeControl(),
+ mBinder.configureVolumeHandling(PLAYBACK_TYPE_REMOTE, volumeProvider.getVolumeControl(),
volumeProvider.getMaxVolume());
} catch (RemoteException e) {
Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
@@ -335,7 +338,7 @@
* @param event The name of the event to send
* @param extras Any extras included with the event
*/
- public void sendSessionEvent(String event, Bundle extras) {
+ public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) {
if (TextUtils.isEmpty(event)) {
throw new IllegalArgumentException("event cannot be null or empty");
}
@@ -367,7 +370,7 @@
* @return A token that can be used to create a MediaController for this
* session
*/
- public MediaSessionToken getSessionToken() {
+ public @NonNull MediaSessionToken getSessionToken() {
return mSessionToken;
}
@@ -512,7 +515,7 @@
*
* @param state The current state of playback
*/
- public void setPlaybackState(PlaybackState state) {
+ public void setPlaybackState(@Nullable PlaybackState state) {
try {
mBinder.setPlaybackState(state);
} catch (RemoteException e) {
@@ -526,7 +529,7 @@
*
* @param metadata The new metadata
*/
- public void setMetadata(MediaMetadata metadata) {
+ public void setMetadata(@Nullable MediaMetadata metadata) {
try {
mBinder.setMetadata(metadata);
} catch (RemoteException e) {
@@ -714,7 +717,7 @@
* @param mediaButtonIntent an intent containing the KeyEvent as an
* extra
*/
- public void onMediaButtonEvent(Intent mediaButtonIntent) {
+ public void onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
}
/**
@@ -722,10 +725,12 @@
* The owner of the session may handle custom commands but is not
* required to.
*
- * @param command
- * @param extras optional
+ * @param command The command name.
+ * @param extras Optional parameters for the command, may be null.
+ * @param cb A result receiver to which a result may be sent by the command, may be null.
*/
- public void onControlCommand(String command, Bundle extras, ResultReceiver cb) {
+ public void onControlCommand(@NonNull String command, @Nullable Bundle extras,
+ @Nullable ResultReceiver cb) {
}
/**
@@ -831,7 +836,7 @@
*
* @param rating
*/
- public void onSetRating(Rating rating) {
+ public void onSetRating(@NonNull Rating rating) {
}
/**
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 1ff49d8..9291bb0 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -16,14 +16,18 @@
package android.media.session;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.media.IRemoteVolumeController;
import android.media.session.ISessionManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
+import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
@@ -63,10 +67,10 @@
/**
* Creates a new session.
*
- * @param tag A short name for debugging purposes
- * @return a {@link MediaSession} for the new session
+ * @param tag A short name for debugging purposes.
+ * @return A {@link MediaSession} for the new session.
*/
- public MediaSession createSession(String tag) {
+ public @NonNull MediaSession createSession(@NonNull String tag) {
return createSessionAsUser(tag, UserHandle.myUserId());
}
@@ -76,12 +80,16 @@
* {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
* permission.
*
- * @param tag A short name for debugging purposes
+ * @param tag A short name for debugging purposes.
* @param userId The user id to create the session as.
- * @return a {@link MediaSession} for the new session
+ * @return A {@link MediaSession} for the new session.
* @hide
*/
- public MediaSession createSessionAsUser(String tag, int userId) {
+ public @NonNull MediaSession createSessionAsUser(@NonNull String tag, int userId) {
+ if (TextUtils.isEmpty(tag)) {
+ throw new IllegalArgumentException("tag must not be null or empty");
+ }
+
try {
MediaSession.CallbackStub cbStub = new MediaSession.CallbackStub();
MediaSession session = new MediaSession(mService
@@ -108,9 +116,10 @@
*
* @param notificationListener The enabled notification listener component.
* May be null.
- * @return A list of controllers for ongoing sessions
+ * @return A list of controllers for ongoing sessions.
*/
- public List<MediaController> getActiveSessions(ComponentName notificationListener) {
+ public @NonNull List<MediaController> getActiveSessions(
+ @Nullable ComponentName notificationListener) {
return getActiveSessionsForUser(notificationListener, UserHandle.myUserId());
}
@@ -127,8 +136,8 @@
* @return A list of controllers for ongoing sessions.
* @hide
*/
- public List<MediaController> getActiveSessionsForUser(ComponentName notificationListener,
- int userId) {
+ public @NonNull List<MediaController> getActiveSessionsForUser(
+ @Nullable ComponentName notificationListener, int userId) {
ArrayList<MediaController> controllers = new ArrayList<MediaController>();
try {
List<IBinder> binders = mService.getSessions(notificationListener, userId);
@@ -177,8 +186,8 @@
* @param userId The userId to listen for changes on.
* @hide
*/
- public void addActiveSessionsListener(SessionListener sessionListener,
- ComponentName notificationListener, int userId) {
+ public void addActiveSessionsListener(@NonNull SessionListener sessionListener,
+ @Nullable ComponentName notificationListener, int userId) {
if (sessionListener == null) {
throw new IllegalArgumentException("listener may not be null");
}
@@ -195,7 +204,7 @@
* @param listener The listener to remove.
* @hide
*/
- public void removeActiveSessionsListener(SessionListener listener) {
+ public void removeActiveSessionsListener(@NonNull SessionListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener may not be null");
}
@@ -207,24 +216,38 @@
}
/**
+ * Set the remote volume controller to receive volume updates on. Only for
+ * use by system UI.
+ *
+ * @param rvc The volume controller to receive updates on.
+ * @hide
+ */
+ public void setRemoteVolumeController(IRemoteVolumeController rvc) {
+ try {
+ mService.setRemoteVolumeController(rvc);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in setRemoteVolumeController.", e);
+ }
+ }
+
+ /**
* Send a media key event. The receiver will be selected automatically.
*
* @param keyEvent The KeyEvent to send.
* @hide
*/
- public void dispatchMediaKeyEvent(KeyEvent keyEvent) {
+ public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent) {
dispatchMediaKeyEvent(keyEvent, false);
}
/**
* Send a media key event. The receiver will be selected automatically.
*
- * @param keyEvent The KeyEvent to send
- * @param needWakeLock true if a wake lock should be held while sending the
- * key
+ * @param keyEvent The KeyEvent to send.
+ * @param needWakeLock True if a wake lock should be held while sending the key.
* @hide
*/
- public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
+ public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean needWakeLock) {
try {
mService.dispatchMediaKeyEvent(keyEvent, needWakeLock);
} catch (RemoteException e) {
@@ -264,7 +287,8 @@
* @param controllers The updated list of controllers for the user that
* changed.
*/
- public abstract void onActiveSessionsChanged(List<MediaController> controllers);
+ public abstract void onActiveSessionsChanged(
+ @Nullable List<MediaController> controllers);
private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() {
@Override
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 3b3f249..6125cb4 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -561,8 +561,8 @@
return 0;
}
- public static final Parcelable.Creator<PlaybackState> CREATOR
- = new Parcelable.Creator<PlaybackState>() {
+ public static final Parcelable.Creator<PlaybackState> CREATOR =
+ new Parcelable.Creator<PlaybackState>() {
@Override
public PlaybackState createFromParcel(Parcel in) {
return new PlaybackState(in);
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 409a33c..0f4a930 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -560,8 +560,11 @@
*/
int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
+ boolean isNavigationKey = false;
if (event instanceof KeyEvent) {
- if (((KeyEvent) event).dispatch(this, mDispatcherState, this)) {
+ KeyEvent keyEvent = (KeyEvent) event;
+ isNavigationKey = isNavigationKey(keyEvent.getKeyCode());
+ if (keyEvent.dispatch(this, mDispatcherState, this)) {
return TvInputManager.Session.DISPATCH_HANDLED;
}
} else if (event instanceof MotionEvent) {
@@ -587,8 +590,18 @@
if (!mOverlayView.hasWindowFocus()) {
mOverlayView.getViewRootImpl().windowFocusChanged(true, true);
}
- mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
- return TvInputManager.Session.DISPATCH_IN_PROGRESS;
+ if (isNavigationKey && mOverlayView.hasFocusable()) {
+ // If mOverlayView has focusable views, navigation key events should be always
+ // handled. If not, it can make the application UI navigation messed up.
+ // For example, in the case that the left-most view is focused, a left key event
+ // will not be handled in ViewRootImpl. Then, the left key event will be handled in
+ // the application during the UI navigation of the TV input.
+ mOverlayView.getViewRootImpl().dispatchInputEvent(event);
+ return TvInputManager.Session.DISPATCH_HANDLED;
+ } else {
+ mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
+ return TvInputManager.Session.DISPATCH_IN_PROGRESS;
+ }
}
private void setSessionCallback(ITvInputSessionCallback callback) {
@@ -596,6 +609,26 @@
}
}
+ /** @hide */
+ public static boolean isNavigationKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_PAGE_UP:
+ case KeyEvent.KEYCODE_PAGE_DOWN:
+ case KeyEvent.KEYCODE_MOVE_HOME:
+ case KeyEvent.KEYCODE_MOVE_END:
+ case KeyEvent.KEYCODE_TAB:
+ case KeyEvent.KEYCODE_SPACE:
+ case KeyEvent.KEYCODE_ENTER:
+ return true;
+ }
+ return false;
+ }
+
private final class ServiceHandler extends Handler {
private static final int DO_CREATE_SESSION = 1;
private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2;
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 2831d9e..a913e59c 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -33,6 +33,7 @@
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
+import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
@@ -339,6 +340,11 @@
public void setVisibility(int visibility) {
super.setVisibility(visibility);
mSurfaceView.setVisibility(visibility);
+ if (visibility == View.VISIBLE) {
+ createSessionOverlayView();
+ } else {
+ removeSessionOverlayView();
+ }
}
private void release() {
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 41ed9e1..ad7ee7a 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -33,6 +33,9 @@
#include <jni.h>
#include <JNIHelp.h>
+#include <stdint.h>
+#include <inttypes.h>
+
#define ALIGN(x, mask) ( ((x) + (mask) - 1) & ~((mask) - 1) )
#define ANDROID_MEDIA_IMAGEREADER_CTX_JNI_ID "mNativeContext"
@@ -300,6 +303,14 @@
// failed to find size, default to whole buffer
if (size == 0) {
+ /*
+ * This is a problem because not including the JPEG header
+ * means that in certain rare situations a regular JPEG blob
+ * will be misidentified as having a header, in which case
+ * we will get a garbage size value.
+ */
+ ALOGW("%s: No JPEG header detected, defaulting to size=width=%d",
+ __FUNCTION__, width);
size = width;
}
@@ -848,6 +859,14 @@
// Create byteBuffer from native buffer
Image_getLockedBufferInfo(env, buffer, idx, &base, &size);
+
+ if (size > static_cast<uint32_t>(INT32_MAX)) {
+ // Byte buffer have 'int capacity', so check the range
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "Size too large for bytebuffer capacity " PRIu32, size);
+ return NULL;
+ }
+
byteBuffer = env->NewDirectByteBuffer(base, size);
// TODO: throw dvm exOutOfMemoryError?
if ((byteBuffer == NULL) && (env->ExceptionCheck() == false)) {
diff --git a/media/mca/filterfw/native/core/gl_env.cpp b/media/mca/filterfw/native/core/gl_env.cpp
index f092af8..c9d33da 100644
--- a/media/mca/filterfw/native/core/gl_env.cpp
+++ b/media/mca/filterfw/native/core/gl_env.cpp
@@ -165,7 +165,8 @@
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
- surfaceTexture_ = new GLConsumer(consumer, 0);
+ surfaceTexture_ = new GLConsumer(consumer, 0, GLConsumer::TEXTURE_EXTERNAL,
+ true, false);
window_ = new Surface(producer);
surfaces_[0] = SurfaceWindowPair(eglCreateWindowSurface(display(), config, window_.get(), NULL), NULL);
diff --git a/packages/DocumentsUI/res/values-km-rKH/strings.xml b/packages/DocumentsUI/res/values-km-rKH/strings.xml
index e8944ec..8c9030d 100644
--- a/packages/DocumentsUI/res/values-km-rKH/strings.xml
+++ b/packages/DocumentsUI/res/values-km-rKH/strings.xml
@@ -20,14 +20,14 @@
<string name="title_open" msgid="4353228937663917801">"បើកពី"</string>
<string name="title_save" msgid="2433679664882857999">"រក្សាទុកទៅ"</string>
<string name="menu_create_dir" msgid="5947289605844398389">"បង្កើតថត"</string>
- <string name="menu_grid" msgid="6878021334497835259">"ទិដ្ឋភាពក្រឡា"</string>
+ <string name="menu_grid" msgid="6878021334497835259">"ទិដ្ឋភាពក្រឡា"</string>
<string name="menu_list" msgid="7279285939892417279">"ទិដ្ឋភាពបញ្ជី"</string>
<string name="menu_sort" msgid="7677740407158414452">"តម្រៀបតាម"</string>
<string name="menu_search" msgid="3816712084502856974">"ស្វែងរក"</string>
<string name="menu_settings" msgid="6008033148948428823">"ការកំណត់"</string>
<string name="menu_open" msgid="432922957274920903">"បើក"</string>
<string name="menu_save" msgid="2394743337684426338">"រក្សាទុក"</string>
- <string name="menu_share" msgid="3075149983979628146">"ចែករំលែក"</string>
+ <string name="menu_share" msgid="3075149983979628146">"ចែករំលែក"</string>
<string name="menu_delete" msgid="8138799623850614177">"លុប"</string>
<string name="menu_select" msgid="8711270657353563424">"ជ្រើស \"<xliff:g id="DIRECTORY">^1</xliff:g>\""</string>
<string name="mode_selected_count" msgid="459111894725594625">"បានជ្រើស <xliff:g id="COUNT">%1$d</xliff:g>"</string>
@@ -48,7 +48,7 @@
<string name="pref_advanced_devices" msgid="903257239609301276">"បង្ហាញឧបករណ៍កម្រិតខ្ពស់"</string>
<string name="pref_file_size" msgid="2826879315743961459">"បង្ហាញទំហំឯកសារ"</string>
<string name="pref_device_size" msgid="3542106883278997222">"បង្ហាញទំហំឧបករណ៍"</string>
- <string name="empty" msgid="7858882803708117596">"គ្មានធាតុ"</string>
+ <string name="empty" msgid="7858882803708117596">"គ្មានធាតុ"</string>
<string name="toast_no_application" msgid="1339885974067891667">"មិនអាចបើកឯកសារ"</string>
<string name="toast_failed_delete" msgid="2180678019407244069">"មិនអាចលុបឯកសារមួយចំនួន"</string>
<string name="share_via" msgid="8966594246261344259">"ចែករំលែកតាម"</string>
diff --git a/packages/Keyguard/res/values-da/strings.xml b/packages/Keyguard/res/values-da/strings.xml
index 85ebf4b..1acfa6b 100644
--- a/packages/Keyguard/res/values-da/strings.xml
+++ b/packages/Keyguard/res/values-da/strings.xml
@@ -85,7 +85,7 @@
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Annuller"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Slet"</string>
- <string name="keyboardview_keycode_done" msgid="1992571118466679775">"Udført"</string>
+ <string name="keyboardview_keycode_done" msgid="1992571118466679775">"Udfør"</string>
<string name="keyboardview_keycode_mode_change" msgid="4547387741906537519">"Ændring af tilstand"</string>
<string name="keyboardview_keycode_shift" msgid="2270748814315147690">"Shift"</string>
<string name="keyboardview_keycode_enter" msgid="2985864015076059467">"Angiv"</string>
diff --git a/packages/Keyguard/res/values-km-rKH/strings.xml b/packages/Keyguard/res/values-km-rKH/strings.xml
index ecdad8c..c26b1b4 100644
--- a/packages/Keyguard/res/values-km-rKH/strings.xml
+++ b/packages/Keyguard/res/values-km-rKH/strings.xml
@@ -83,7 +83,7 @@
<string name="password_keyboard_label_alpha_key" msgid="8001096175167485649">"ABC"</string>
<string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
- <string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"បោះបង់"</string>
+ <string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"បោះបង់"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"លុប"</string>
<string name="keyboardview_keycode_done" msgid="1992571118466679775">"រួចរាល់"</string>
<string name="keyboardview_keycode_mode_change" msgid="4547387741906537519">"ប្ដូររបៀប"</string>
@@ -120,7 +120,7 @@
<string name="kg_login_too_many_attempts" msgid="6486842094005698475">"ព្យាយាមលំនាំច្រើនពេក"</string>
<string name="kg_login_instructions" msgid="1100551261265506448">"ដើម្បីដោះសោ ចូលក្នុងគណនី Google ។"</string>
<string name="kg_login_username_hint" msgid="5718534272070920364">"ឈ្មោះអ្នកប្រើ (អ៊ីម៉ែល)"</string>
- <string name="kg_login_password_hint" msgid="9057289103827298549">"ពាក្យសម្ងាត់"</string>
+ <string name="kg_login_password_hint" msgid="9057289103827298549">"ពាក្យសម្ងាត់"</string>
<string name="kg_login_submit_button" msgid="5355904582674054702">"ចូល"</string>
<string name="kg_login_invalid_input" msgid="5754664119319872197">"ឈ្មោះអ្នកប្រើ ឬពាក្យសម្ងាត់មិនត្រឹមត្រូវ។"</string>
<string name="kg_login_account_recovery_hint" msgid="5690709132841752974">"ភ្លេចឈ្មោះអ្នកប្រើ ឬពាក្យសម្ងាត់របស់អ្នក?\nមើល "<b>"google.com/accounts/recovery"</b>" ។"</string>
diff --git a/packages/PrintSpooler/res/values-km-rKH/strings.xml b/packages/PrintSpooler/res/values-km-rKH/strings.xml
index 0146ab7..cdcb21f 100644
--- a/packages/PrintSpooler/res/values-km-rKH/strings.xml
+++ b/packages/PrintSpooler/res/values-km-rKH/strings.xml
@@ -60,7 +60,7 @@
</plurals>
<string name="cancel" msgid="4373674107267141885">"បោះបង់"</string>
<string name="restart" msgid="2472034227037808749">"ចាប់ផ្ដើមឡើងវិញ"</string>
- <string name="no_connection_to_printer" msgid="2159246915977282728">"គ្មានការភ្ជាប់ទៅម៉ាស៊ីនបោះពុម្ព"</string>
+ <string name="no_connection_to_printer" msgid="2159246915977282728">"គ្មានការភ្ជាប់ទៅម៉ាស៊ីនបោះពុម្ព"</string>
<string name="reason_unknown" msgid="5507940196503246139">"មិនស្គាល់"</string>
<string name="printer_unavailable" msgid="2434170617003315690">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> – មិនអាចប្រើបាន"</string>
<string-array name="color_mode_labels">
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e9cb197..0df6c74 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -300,5 +300,11 @@
<category android:name="android.intent.category.DESK_DOCK" />
</intent-filter>
</activity>
+
+
+ <!-- I dream of notifications -->
+ <service
+ android:name=".doze.DozeService"
+ android:exported="true" />
</application>
</manifest>
diff --git a/packages/SystemUI/res/drawable/ic_notify_zen.xml b/packages/SystemUI/res/drawable/ic_notify_zen.xml
deleted file mode 100644
index c46455b..0000000
--- a/packages/SystemUI/res/drawable/ic_notify_zen.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="24dp"
- android:height="24dp"/>
-
- <viewport
- android:viewportWidth="48.0"
- android:viewportHeight="48.0"/>
-
- <path
- android:fill="#FFFFFFFF"
- android:pathData="M4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0s20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0S4.0,13.0 4.0,24.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8zM8.0,24.0c0.0,-3.7 1.3,-7.1 3.4,-9.8L33.8,36.6C31.1,38.7 27.7,40.0 24.0,40.0C15.2,40.0 8.0,32.8 8.0,24.0z"/>
-</vector>
diff --git a/packages/SystemUI/res/layout/recents.xml b/packages/SystemUI/res/layout/recents.xml
new file mode 100644
index 0000000..47740ee
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <!-- Status Bar Scrim View -->
+ <ImageView
+ android:id="@+id/status_bar_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|top"
+ android:scaleType="fitXY"
+ android:src="@drawable/recents_status_gradient" />
+
+ <!-- Recents View -->
+ <com.android.systemui.recents.views.RecentsView
+ android:id="@+id/recents_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true" />
+
+ <!-- Empty View -->
+ <ViewStub android:id="@+id/empty_view_stub"
+ android:layout="@layout/recents_empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- Fullscreen Transition View -->
+ <ViewStub android:id="@+id/fullscreen_overlay_stub"
+ android:layout="@layout/recents_fullscreen_overlay"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- Nav Bar Scrim View -->
+ <ImageView
+ android:id="@+id/nav_bar_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|bottom"
+ android:scaleType="fitXY"
+ android:src="@drawable/recents_lower_gradient" />
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_empty.xml b/packages/SystemUI/res/layout/recents_empty.xml
index c0ddde1..21d1711 100644
--- a/packages/SystemUI/res/layout/recents_empty.xml
+++ b/packages/SystemUI/res/layout/recents_empty.xml
@@ -16,11 +16,13 @@
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
android:gravity="center"
android:textSize="20sp"
android:textColor="#ffffffff"
android:text="@string/recents_empty_message"
android:fontFamily="sans-serif-light"
+ android:background="#80000000"
android:visibility="gone" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_status_bar_scrim.xml b/packages/SystemUI/res/layout/recents_fullscreen_overlay.xml
similarity index 66%
rename from packages/SystemUI/res/layout/recents_status_bar_scrim.xml
rename to packages/SystemUI/res/layout/recents_fullscreen_overlay.xml
index 24928d0..1d021f9 100644
--- a/packages/SystemUI/res/layout/recents_status_bar_scrim.xml
+++ b/packages/SystemUI/res/layout/recents_fullscreen_overlay.xml
@@ -14,10 +14,14 @@
limitations under the License.
-->
-<ImageView
+<com.android.systemui.recents.views.FullscreenTransitionOverlayView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal|top"
- android:scaleType="fitXY"
- android:src="@drawable/recents_status_gradient" />
\ No newline at end of file
+ android:layout_height="match_parent"
+ android:visibility="gone">
+ <ImageView
+ android:id="@+id/image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="fitXY" />
+</com.android.systemui.recents.views.FullscreenTransitionOverlayView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_nav_bar_scrim.xml b/packages/SystemUI/res/layout/recents_nav_bar_scrim.xml
deleted file mode 100644
index 4245d49..0000000
--- a/packages/SystemUI/res/layout/recents_nav_bar_scrim.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<ImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal|bottom"
- android:scaleType="fitXY"
- android:src="@drawable/recents_lower_gradient" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 4e38da6..19d72c4 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -38,8 +38,8 @@
<color name="system_secondary_color">#ff384248</color>
<color name="system_accent_color">#ff7fcac3</color>
<color name="system_error_color">#fff0592b</color>
- <color name="qs_tile_divider">#29ffffff</color><!-- 16% white -->
- <color name="qs_tile_text">#FFFFFFFF</color>
+ <color name="qs_tile_divider">#29ffffff</color><!-- 16% white -->
+ <color name="qs_tile_text">#B3FFFFFF</color><!-- 70% white -->
<color name="status_bar_clock_color">#FFFFFFFF</color>
<!-- Tint color for the content on the notification overflow card. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 36c1994..370ff1c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -97,34 +97,6 @@
<!-- half the distance between notifications in the panel -->
<dimen name="notification_divider_height">2dp</dimen>
- <!-- Notification drawer tuning parameters (phone UI) -->
- <!-- Initial velocity of the shade when expanding on its own -->
- <dimen name="self_expand_velocity">2000dp</dimen>
- <!-- Initial velocity of the shade when collapsing on its own -->
- <dimen name="self_collapse_velocity">2000dp</dimen>
- <!-- Minimum final velocity of gestures interpreted as expand requests -->
- <dimen name="fling_expand_min_velocity">100dp</dimen>
- <!-- Minimum final velocity of gestures interpreted as collapse requests -->
- <dimen name="fling_collapse_min_velocity">100dp</dimen>
- <!-- Cap on contribution of x dimension of gesture to overall velocity -->
- <dimen name="fling_gesture_max_x_velocity">200dp</dimen>
- <!-- Cap on overall resulting fling speed (s^-1) -->
- <dimen name="fling_gesture_max_output_velocity">3000dp</dimen>
-
- <!-- Minimum distance a fling must travel (anti-jitter) -->
- <dimen name="fling_gesture_min_dist">20dp</dimen>
-
- <!-- Minimum fraction of the display a gesture must travel, at any velocity, to qualify as a
- collapse request -->
- <item type="dimen" name="collapse_min_display_fraction">10%</item>
- <!-- Minimum fraction of the display a gesture must travel to qualify as an expand request -->
- <item type="dimen" name="expand_min_display_fraction">50%</item>
-
- <!-- Initial acceleration of an expand animation after fling -->
- <dimen name="expand_accel">2000dp</dimen>
- <!-- Initial acceleration of an collapse animation after fling -->
- <dimen name="collapse_accel">2000dp</dimen>
-
<!-- The padding on the global screenshot background image -->
<dimen name="global_screenshot_bg_padding">20dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e5499ee..f021253 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -577,14 +577,6 @@
<!-- Description of the left direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] -->
<string name="description_direction_left">"Slide left for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
- <!-- Zen mode: Summary notification content title. [CHAR LIMIT=NONE] -->
- <plurals name="zen_mode_notification_title">
- <item quantity="one">Notification hidden</item>
- <item quantity="other">%d notifications hidden</item>
- </plurals>
- <!-- Zen mode: Summary notification content text. [CHAR LIMIT=NONE] -->
- <string name="zen_mode_notification_text">Touch to show</string>
-
<!-- Zen mode: Short title. [CHAR LIMIT=40] -->
<string name="zen_mode_title">Do not disturb</string>
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
new file mode 100644
index 0000000..cc0d4a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -0,0 +1,238 @@
+/*
+ * 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.systemui.doze;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
+import android.os.PowerManager;
+import android.os.Vibrator;
+import android.service.dreams.DozeHardware;
+import android.service.dreams.DreamService;
+import android.util.Log;
+
+import com.android.systemui.SystemUIApplication;
+
+public class DozeService extends DreamService {
+ private static final boolean DEBUG = false;
+
+ private static final String TEASE_ACTION = "com.android.systemui.doze.tease";
+
+ private final String mTag = String.format("DozeService.%08x", hashCode());
+ private final Context mContext = this;
+
+ private Host mHost;
+ private DozeHardware mDozeHardware;
+ private SensorManager mSensors;
+ private Sensor mSigMotionSensor;
+ private PowerManager mPowerManager;
+ private PowerManager.WakeLock mWakeLock;
+ private boolean mDreaming;
+ private boolean mTeaseReceiverRegistered;
+
+ public DozeService() {
+ if (DEBUG) Log.d(mTag, "new DozeService()");
+ setDebug(DEBUG);
+ }
+
+ @Override
+ public void onCreate() {
+ if (DEBUG) Log.d(mTag, "onCreate");
+ super.onCreate();
+
+ if (getApplication() instanceof SystemUIApplication) {
+ final SystemUIApplication app = (SystemUIApplication) getApplication();
+ mHost = app.getComponent(Host.class);
+ }
+
+ setWindowless(true);
+
+ mSensors = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
+ mSigMotionSensor = mSensors.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ if (DEBUG) Log.d(mTag, "onAttachedToWindow");
+ super.onAttachedToWindow();
+ }
+
+ @Override
+ public void onDreamingStarted() {
+ super.onDreamingStarted();
+ mDozeHardware = getDozeHardware();
+ if (DEBUG) Log.d(mTag, "onDreamingStarted canDoze=" + canDoze()
+ + " dozeHardware=" + mDozeHardware);
+ mDreaming = true;
+ listenForTeaseSignals(true);
+ requestDoze();
+ }
+
+ public void stayAwake(long millis) {
+ if (mDreaming && millis > 0) {
+ mWakeLock.acquire(millis);
+ }
+ }
+
+ public void startDozing() {
+ if (DEBUG) Log.d(mTag, "startDozing mDreaming=" + mDreaming);
+ if (!mDreaming) {
+ Log.w(mTag, "Not dozing, no longer dreaming");
+ return;
+ }
+
+ super.startDozing();
+ }
+
+ @Override
+ public void onDreamingStopped() {
+ if (DEBUG) Log.d(mTag, "onDreamingStopped isDozing=" + isDozing());
+ super.onDreamingStopped();
+
+ mDreaming = false;
+ mDozeHardware = null;
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ listenForTeaseSignals(false);
+ stopDozing();
+ dozingStopped();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
+ super.onDetachedFromWindow();
+
+ dozingStopped();
+ }
+
+ @Override
+ public void onDestroy() {
+ if (DEBUG) Log.d(mTag, "onDestroy");
+ super.onDestroy();
+
+ dozingStopped();
+ }
+
+ private void requestDoze() {
+ if (mHost != null) {
+ mHost.requestDoze(this);
+ }
+ }
+
+ private void requestTease() {
+ if (mHost != null) {
+ mHost.requestTease(this);
+ }
+ }
+
+ private void dozingStopped() {
+ if (mHost != null) {
+ mHost.dozingStopped(this);
+ }
+ }
+
+ private void listenForTeaseSignals(boolean listen) {
+ if (DEBUG) Log.d(mTag, "listenForTeaseSignals: " + listen);
+ if (mHost == null) return;
+ listenForSignificantMotion(listen);
+ if (listen) {
+ mContext.registerReceiver(mTeaseReceiver, new IntentFilter(TEASE_ACTION));
+ mTeaseReceiverRegistered = true;
+ mHost.addCallback(mHostCallback);
+ } else {
+ if (mTeaseReceiverRegistered) {
+ mContext.unregisterReceiver(mTeaseReceiver);
+ }
+ mTeaseReceiverRegistered = false;
+ mHost.removeCallback(mHostCallback);
+ }
+ }
+
+ private void listenForSignificantMotion(boolean listen) {
+ if (mSigMotionSensor == null) return;
+ if (listen) {
+ mSensors.requestTriggerSensor(mSigMotionListener, mSigMotionSensor);
+ } else {
+ mSensors.cancelTriggerSensor(mSigMotionListener, mSigMotionSensor);
+ }
+ }
+
+ private static String triggerEventToString(TriggerEvent event) {
+ if (event == null) return null;
+ final StringBuilder sb = new StringBuilder("TriggerEvent[")
+ .append(event.timestamp).append(',')
+ .append(event.sensor.getName());
+ if (event.values != null) {
+ for (int i = 0; i < event.values.length; i++) {
+ sb.append(',').append(event.values[i]);
+ }
+ }
+ return sb.append(']').toString();
+ }
+
+ private final TriggerEventListener mSigMotionListener = new TriggerEventListener() {
+ @Override
+ public void onTrigger(TriggerEvent event) {
+ if (DEBUG) Log.d(mTag, "sigMotion.onTrigger: " + triggerEventToString(event));
+ if (DEBUG) {
+ final Vibrator v = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ if (v != null) {
+ v.vibrate(1000);
+ }
+ }
+ requestTease();
+ listenForSignificantMotion(true); // reregister, this sensor only fires once
+ }
+ };
+
+ private final BroadcastReceiver mTeaseReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Log.d(mTag, "Received tease intent");
+ requestTease();
+ }
+ };
+
+ private final Host.Callback mHostCallback = new Host.Callback() {
+ @Override
+ public void onNewNotifications() {
+ if (DEBUG) Log.d(mTag, "onNewNotifications");
+ requestTease();
+ }
+ };
+
+ public interface Host {
+ void addCallback(Callback callback);
+ void removeCallback(Callback callback);
+ void requestDoze(DozeService dozeService);
+ void requestTease(DozeService dozeService);
+ void dozingStopped(DozeService dozeService);
+
+ public interface Callback {
+ void onNewNotifications();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 41b1f75..186b570 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -81,6 +81,8 @@
private long mScreenOffTime;
private int mShowing;
+ private long mBucketDroppedNegativeTimeMs;
+
private boolean mSaver;
private int mSaverTriggerLevel;
private boolean mWarning;
@@ -108,6 +110,11 @@
@Override
public void update(int batteryLevel, int bucket, long screenOffTime) {
mBatteryLevel = batteryLevel;
+ if (bucket >= 0) {
+ mBucketDroppedNegativeTimeMs = 0;
+ } else if (bucket < mBucket) {
+ mBucketDroppedNegativeTimeMs = System.currentTimeMillis();
+ }
mBucket = bucket;
mScreenOffTime = screenOffTime;
mFallbackDialogs.update(batteryLevel, bucket, screenOffTime);
@@ -146,6 +153,7 @@
private void showInvalidChargerNotification() {
final Notification.Builder nb = new Notification.Builder(mContext)
.setSmallIcon(R.drawable.ic_power_low)
+ .setWhen(0)
.setShowWhen(false)
.setOngoing(true)
.setContentTitle(mContext.getString(R.string.invalid_charger_title))
@@ -166,6 +174,8 @@
: R.string.battery_low_percent_format;
final Notification.Builder nb = new Notification.Builder(mContext)
.setSmallIcon(R.drawable.ic_power_low)
+ // Bump the notification when the bucket dropped.
+ .setWhen(mBucketDroppedNegativeTimeMs)
.setShowWhen(false)
.setContentTitle(mContext.getString(R.string.battery_low_title))
.setContentText(mContext.getString(textRes, mBatteryLevel))
@@ -198,6 +208,7 @@
.setContentTitle(mContext.getString(R.string.battery_saver_notification_title))
.setContentText(mContext.getString(R.string.battery_saver_notification_text))
.setOngoing(true)
+ .setWhen(0)
.setShowWhen(false)
.setCategory(Notification.CATEGORY_SYSTEM)
.setVisibility(Notification.VISIBILITY_PUBLIC);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index 8dcdcdb..52ccb59 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -510,7 +510,7 @@
} else {
// If there is no thumbnail transition, but is launching from home into recents, then
// use a quick home transition and do the animation from home
- if (hasRecentTasks && Constants.DebugFlags.App.EnableHomeTransition) {
+ if (hasRecentTasks) {
ActivityOptions opts = getHomeTransitionActivityOptions();
startAlternateRecentsActivity(opts, EXTRA_FROM_HOME);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index ddea0bf..4fb1918 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -25,16 +25,14 @@
public static final boolean Verbose = false;
public static class App {
- // Enables the home->Recents transition
- public static final boolean EnableHomeTransition = true;
// Enables the screenshot app->Recents transition
public static final boolean EnableScreenshotAppTransition = false;
// Enables the filtering of tasks according to their grouping
public static final boolean EnableTaskFiltering = true;
// Enables clipping of tasks against each other
public static final boolean EnableTaskStackClipping = true;
- // Enables the use of theme colors as the task bar background
- public static final boolean EnableTaskBarThemeColors = true;
+ // Enables tapping on the TaskBar to launch the task
+ public static final boolean EnableTaskBarTouchEvents = true;
// Enables app-info pane on long-pressing the icon
public static final boolean EnableDevAppInfoOnLongPress = true;
// Enables the search bar layout
@@ -93,10 +91,8 @@
public static class App {
public static int AppWidgetHostId = 1024;
public static String Key_SearchAppWidgetId = "searchAppWidgetId";
- }
- public static class Window {
- // The dark background dim is set behind the empty recents view
- public static final float DarkBackgroundDim = 0.5f;
+ public static String Key_DebugModeEnabled = "debugModeEnabled";
+ public static String DebugModeVersion = "A";
}
public static class RecentsTaskLoader {
@@ -107,15 +103,6 @@
public static class TaskStackView {
public static final int TaskStackOverscrollRange = 150;
public static final int FilterStartDelay = 25;
-
- // The overlap height relative to the task height
- public static final float StackOverlapPct = 0.65f;
- // The height of the peek space relative to the stack height
- public static final float StackPeekHeightPct = 0.1f;
- // The min scale of the last card in the peek area
- public static final float StackPeekMinScale = 0.8f;
- // The number of cards we see in the peek space
- public static final int StackPeekNumCards = 3;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/DebugTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/DebugTrigger.java
new file mode 100644
index 0000000..d90e2be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/DebugTrigger.java
@@ -0,0 +1,67 @@
+/*
+ * 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.systemui.recents;
+
+import android.os.Handler;
+import android.os.SystemClock;
+import android.view.KeyEvent;
+
+/**
+ * A trigger for catching a debug chord.
+ * We currently use volume up then volume down to trigger this mode.
+ */
+public class DebugTrigger {
+
+ Handler mHandler;
+ Runnable mTriggeredRunnable;
+
+ int mLastKeyCode;
+ long mLastKeyCodeTime;
+
+ public DebugTrigger(Runnable triggeredRunnable) {
+ mHandler = new Handler();
+ mTriggeredRunnable = triggeredRunnable;
+ }
+
+ /** Resets the debug trigger */
+ void reset() {
+ mLastKeyCode = 0;
+ mLastKeyCodeTime = 0;
+ }
+
+ /**
+ * Processes a key event and tests if it is a part of the trigger. If the chord is complete,
+ * then we just call the callback.
+ */
+ public void onKeyEvent(int keyCode) {
+ if (mLastKeyCode == 0) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ mLastKeyCode = keyCode;
+ mLastKeyCodeTime = SystemClock.uptimeMillis();
+ return;
+ }
+ } else {
+ if (mLastKeyCode == KeyEvent.KEYCODE_VOLUME_UP &&
+ keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+ if ((SystemClock.uptimeMillis() - mLastKeyCodeTime) < 750) {
+ mTriggeredRunnable.run();
+ }
+ }
+ }
+ reset();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/DozeTrigger.java b/packages/SystemUI/src/com/android/systemui/recents/DozeTrigger.java
index 037af14..f0b2cb6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/DozeTrigger.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/DozeTrigger.java
@@ -49,7 +49,7 @@
/** Starts dozing. This also resets the trigger flag. */
public void startDozing() {
- poke();
+ forcePoke();
mHasTriggered = false;
}
@@ -59,8 +59,15 @@
mIsDozing = false;
}
- /** Poke this dozer to wake it up for a little bit. */
+ /** Poke this dozer to wake it up for a little bit, if it is dozing. */
public void poke() {
+ if (mIsDozing) {
+ forcePoke();
+ }
+ }
+
+ /** Poke this dozer to wake it up for a little bit. */
+ void forcePoke() {
mHandler.removeCallbacks(mDozeRunnable);
mHandler.postDelayed(mDozeRunnable, mDozeDurationSeconds * 1000);
mIsDozing = true;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 4a02bac3..4b4cf01 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -18,6 +18,7 @@
import android.app.Activity;
import android.app.ActivityOptions;
+import android.app.SearchManager;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
@@ -25,30 +26,51 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Pair;
-import android.view.Gravity;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
+import android.view.ViewStub;
+import android.widget.Toast;
import com.android.systemui.R;
+import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.SpaceNode;
import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.FullScreenTransitionView;
+import com.android.systemui.recents.views.FullscreenTransitionOverlayView;
import com.android.systemui.recents.views.RecentsView;
+import com.android.systemui.recents.views.SystemBarScrimViews;
import com.android.systemui.recents.views.ViewAnimation;
import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.util.ArrayList;
/* Activity */
public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks,
- FullScreenTransitionView.FullScreenTransitionViewCallbacks {
+ FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks {
+
+ RecentsView mRecentsView;
+ SystemBarScrimViews mScrimViews;
+ ViewStub mEmptyViewStub;
+ View mEmptyView;
+ ViewStub mFullscreenOverlayStub;
+ FullscreenTransitionOverlayView mFullScreenOverlayView;
+
+ RecentsConfiguration mConfig;
+
+ RecentsAppWidgetHost mAppWidgetHost;
+ AppWidgetProviderInfo mSearchAppWidgetInfo;
+ AppWidgetHostView mSearchAppWidgetHostView;
+
+ boolean mVisible;
+ boolean mTaskLaunched;
+
+ // Runnables to finish the Recents activity
+ FinishRecentsRunnable mFinishRunnable = new FinishRecentsRunnable(true);
+ FinishRecentsRunnable mFinishWithoutAnimationRunnable = new FinishRecentsRunnable(false);
+ FinishRecentsRunnable mFinishLaunchHomeRunnable;
/**
* A Runnable to finish Recents either with/without a transition, and either by calling finish()
@@ -63,6 +85,10 @@
mUseCustomFinishTransition = withTransition;
}
+ /**
+ * Creates a finish runnable that starts the specified intent, using the given
+ * ActivityOptions.
+ */
public FinishRecentsRunnable(Intent launchIntent, ActivityOptions opts) {
mLaunchIntent = launchIntent;
mLaunchOpts = opts;
@@ -90,42 +116,8 @@
}
}
- FrameLayout mContainerView;
- RecentsView mRecentsView;
- View mEmptyView;
- View mStatusBarScrimView;
- View mNavBarScrimView;
- FullScreenTransitionView mFullScreenshotView;
-
- RecentsConfiguration mConfig;
-
- RecentsAppWidgetHost mAppWidgetHost;
- AppWidgetProviderInfo mSearchAppWidgetInfo;
- AppWidgetHostView mSearchAppWidgetHostView;
-
- boolean mVisible;
- boolean mTaskLaunched;
-
- // Runnables to finish the Recents activity
- FinishRecentsRunnable mFinishRunnable = new FinishRecentsRunnable(true);
- FinishRecentsRunnable mFinishWithoutAnimationRunnable = new FinishRecentsRunnable(false);
- FinishRecentsRunnable mFinishLaunchHomeRunnable;
-
- private static Method sPropertyMethod;
- static {
- try {
- Class<?> c = Class.forName("android.view.GLES20Canvas");
- sPropertyMethod = c.getDeclaredMethod("setProperty", String.class, String.class);
- if (!sPropertyMethod.isAccessible()) sPropertyMethod.setAccessible(true);
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- }
- }
-
// Broadcast receiver to handle messages from our RecentsService
- BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
+ final BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
@@ -139,8 +131,8 @@
dismissRecentsIfVisible();
} else {
// If we are mid-animation into Recents, then reverse it and finish
- if (mFullScreenshotView == null ||
- !mFullScreenshotView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
+ if (mFullScreenOverlayView == null ||
+ !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
// Otherwise, either finish Recents, or launch Home directly
ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(context,
null, mFinishLaunchHomeRunnable, null);
@@ -156,21 +148,32 @@
}
} else if (action.equals(RecentsService.ACTION_START_ENTER_ANIMATION)) {
// Try and start the enter animation (or restart it on configuration changed)
- mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenshotView));
+ mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenOverlayView));
// Call our callback
onEnterAnimationTriggered();
+ } else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) {
+ // Refresh the search widget
+ refreshSearchWidget();
}
}
};
// Broadcast receiver to handle messages from the system
- BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
+ final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mFinishWithoutAnimationRunnable.run();
}
};
+ // Debug trigger
+ final DebugTrigger mDebugTrigger = new DebugTrigger(new Runnable() {
+ @Override
+ public void run() {
+ onDebugModeTriggered();
+ }
+ });
+
/** Updates the set of recent tasks */
void updateRecentsTasks(Intent launchIntent) {
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
@@ -192,15 +195,14 @@
mConfig.launchedWithNoRecentTasks = !root.hasTasks();
// Show the scrim if we animate into Recents without window transitions
- mNavBarScrimView.setVisibility(mConfig.hasNavBarScrim() &&
- !mConfig.shouldAnimateNavBarScrim() ? View.VISIBLE : View.INVISIBLE);
- mStatusBarScrimView.setVisibility(mConfig.hasStatusBarScrim() &&
- !mConfig.shouldAnimateStatusBarScrim() ? View.VISIBLE : View.INVISIBLE);
+ mScrimViews.prepareEnterRecentsAnimation();
// Add the default no-recents layout
+ if (mEmptyView == null) {
+ mEmptyView = mEmptyViewStub.inflate();
+ }
if (mConfig.launchedWithNoRecentTasks) {
mEmptyView.setVisibility(View.VISIBLE);
- mEmptyView.setBackgroundColor(0x80000000);
} else {
mEmptyView.setVisibility(View.GONE);
}
@@ -283,8 +285,8 @@
boolean dismissRecentsIfVisible() {
if (mVisible) {
// If we are mid-animation into Recents, then reverse it and finish
- if (mFullScreenshotView == null ||
- !mFullScreenshotView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
+ if (mFullScreenOverlayView == null ||
+ !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
// If we have a focused task, then launch that task
if (!mRecentsView.launchFocusedTask()) {
if (mConfig.launchedFromHome) {
@@ -335,50 +337,25 @@
// Initialize the widget host (the host id is static and does not change)
mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
- // Create the view hierarchy
- mRecentsView = new RecentsView(this);
+ // Set the Recents layout
+ setContentView(R.layout.recents);
+ mRecentsView = (RecentsView) findViewById(R.id.recents_view);
mRecentsView.setCallbacks(this);
- mRecentsView.setLayoutParams(new FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.MATCH_PARENT));
mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
-
- // Create the empty view
- LayoutInflater inflater = LayoutInflater.from(this);
- mEmptyView = inflater.inflate(R.layout.recents_empty, mContainerView, false);
- mStatusBarScrimView = inflater.inflate(R.layout.recents_status_bar_scrim, mContainerView, false);
- mStatusBarScrimView.setLayoutParams(new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.TOP));
- mNavBarScrimView = inflater.inflate(R.layout.recents_nav_bar_scrim, mContainerView, false);
- mNavBarScrimView.setLayoutParams(new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM));
- if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
- mFullScreenshotView = new FullScreenTransitionView(this, this);
- mFullScreenshotView.setLayoutParams(new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
- }
-
- // Add the views to the layout
- mContainerView = new FrameLayout(this);
- mContainerView.addView(mStatusBarScrimView);
- mContainerView.addView(mRecentsView);
- mContainerView.addView(mEmptyView);
- if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
- mContainerView.addView(mFullScreenshotView);
- }
- mContainerView.addView(mNavBarScrimView);
- setContentView(mContainerView);
+ mEmptyViewStub = (ViewStub) findViewById(R.id.empty_view_stub);
+ mFullscreenOverlayStub = (ViewStub) findViewById(R.id.fullscreen_overlay_stub);
+ mScrimViews = new SystemBarScrimViews(this, mConfig);
// Update the recent tasks
updateRecentsTasks(getIntent());
// Prepare the screenshot transition if necessary
if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
- mFullScreenshotView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
+ mFullScreenOverlayView = (FullscreenTransitionOverlayView) mFullscreenOverlayStub.inflate();
+ mFullScreenOverlayView.setCallbacks(this);
+ mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
}
// Bind the search app widget when we first start up
@@ -391,10 +368,10 @@
onConfigurationChange();
}
- // XXX: Update the shadows
+ // Private API calls to make the shadows look better
try {
- sPropertyMethod.invoke(null, "ambientShadowStrength", String.valueOf(35f));
- sPropertyMethod.invoke(null, "ambientRatio", String.valueOf(0.5f));
+ Utilities.setShadowProperty("ambientShadowStrength", String.valueOf(35f));
+ Utilities.setShadowProperty("ambientRatio", String.valueOf(0.5f));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
@@ -404,7 +381,7 @@
void onConfigurationChange() {
// Try and start the enter animation (or restart it on configuration changed)
- mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenshotView));
+ mRecentsView.startEnterRecentsAnimation(new ViewAnimation.TaskViewEnterContext(mFullScreenOverlayView));
// Call our callback
onEnterAnimationTriggered();
}
@@ -432,7 +409,7 @@
// Prepare the screenshot transition if necessary
if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
- mFullScreenshotView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
+ mFullScreenOverlayView.prepareAnimateOnEnterRecents(AlternateRecentsComponent.getLastScreenshot());
}
// Don't attempt to rebind the search bar widget, but just add the search bar layout
@@ -473,6 +450,7 @@
filter.addAction(RecentsService.ACTION_HIDE_RECENTS_ACTIVITY);
filter.addAction(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY);
filter.addAction(RecentsService.ACTION_START_ENTER_ANIMATION);
+ filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
registerReceiver(mServiceBroadcastReceiver, filter);
// Register the broadcast receiver to handle messages when the screen is turned off
@@ -555,7 +533,8 @@
mRecentsView.focusNextTask(!backward);
return true;
}
-
+ // Pass through the debug trigger
+ mDebugTrigger.onKeyEvent(keyCode);
return super.onKeyDown(keyCode, event);
}
@@ -567,8 +546,8 @@
@Override
public void onBackPressed() {
// If we are mid-animation into Recents, then reverse it and finish
- if (mFullScreenshotView == null ||
- !mFullScreenshotView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
+ if (mFullScreenOverlayView == null ||
+ !mFullScreenOverlayView.cancelAnimateOnEnterRecents(mFinishRunnable)) {
// If we are currently filtering in any stacks, unfilter them first
if (!mRecentsView.unfilterFilteredStacks()) {
if (mConfig.launchedFromHome) {
@@ -588,48 +567,36 @@
}
}
- public void onEnterAnimationTriggered() {
- // Fade in the scrims
- if (mConfig.hasStatusBarScrim() && mConfig.shouldAnimateStatusBarScrim()) {
- mStatusBarScrimView.setTranslationY(-mStatusBarScrimView.getMeasuredHeight());
- mStatusBarScrimView.animate()
- .translationY(0)
- .setStartDelay(mConfig.taskBarEnterAnimDelay)
- .setDuration(mConfig.navBarScrimEnterDuration)
- .setInterpolator(mConfig.quintOutInterpolator)
- .withStartAction(new Runnable() {
- @Override
- public void run() {
- mStatusBarScrimView.setVisibility(View.VISIBLE);
- }
- })
- .start();
- }
- if (mConfig.hasNavBarScrim() && mConfig.shouldAnimateNavBarScrim()) {
- mNavBarScrimView.setTranslationY(mNavBarScrimView.getMeasuredHeight());
- mNavBarScrimView.animate()
- .translationY(0)
- .setStartDelay(mConfig.taskBarEnterAnimDelay)
- .setDuration(mConfig.navBarScrimEnterDuration)
- .setInterpolator(mConfig.quintOutInterpolator)
- .withStartAction(new Runnable() {
- @Override
- public void run() {
- mNavBarScrimView.setVisibility(View.VISIBLE);
- }
- })
- .start();
+ /** Called when debug mode is triggered */
+ public void onDebugModeTriggered() {
+ if (mConfig.developerOptionsEnabled) {
+ SharedPreferences settings = getSharedPreferences(getPackageName(), 0);
+ if (settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false)) {
+ // Disable the debug mode
+ settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply();
+ } else {
+ // Enable the debug mode
+ settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply();
+ }
+ Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion +
+ ") toggled, please restart Recents now", Toast.LENGTH_SHORT).show();
}
}
- /**** FullScreenTransitionView.FullScreenTransitionViewCallbacks Implementation ****/
+ /** Called when the enter recents animation is triggered. */
+ public void onEnterAnimationTriggered() {
+ // Animate the scrims in
+ mScrimViews.startEnterRecentsAnimation();
+ }
+
+ /**** FullscreenTransitionOverlayView.FullScreenTransitionViewCallbacks Implementation ****/
@Override
public void onEnterAnimationComplete(boolean canceled) {
if (!canceled) {
// Reset the full screenshot transition view
if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
- mFullScreenshotView.reset();
+ mFullScreenOverlayView.reset();
}
// XXX: We should clean up the screenshot in this case as well, but it needs to happen
@@ -642,16 +609,9 @@
/**** RecentsView.RecentsViewCallbacks Implementation ****/
@Override
- public void onExitAnimationTriggered() {
- // Fade out the scrim
- if (mConfig.hasNavBarScrim() && mConfig.shouldAnimateNavBarScrim()) {
- mNavBarScrimView.animate()
- .translationY(mNavBarScrimView.getMeasuredHeight())
- .setStartDelay(0)
- .setDuration(mConfig.taskBarExitAnimDuration)
- .setInterpolator(mConfig.fastOutSlowInInterpolator)
- .start();
- }
+ public void onExitToHomeAnimationTriggered() {
+ // Animate the scrims out
+ mScrimViews.startExitRecentsAnimation();
}
@Override
@@ -662,6 +622,11 @@
AlternateRecentsComponent.notifyVisibilityChanged(false);
}
+ @Override
+ public void onLastTaskRemoved() {
+ mFinishLaunchHomeRunnable.run();
+ }
+
/**** RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks Implementation ****/
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
index d55eba7..3754340 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
@@ -19,6 +19,7 @@
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetProviderInfo;
import android.content.Context;
+import com.android.systemui.recents.model.RecentsTaskLoader;
/** Our special app widget host for the Search widget */
public class RecentsAppWidgetHost extends AppWidgetHost {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 10978ca..d57f779 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -103,6 +103,7 @@
/** Dev options */
public boolean developerOptionsEnabled;
+ public boolean debugModeEnabled;
/** Private constructor */
private RecentsConfiguration(Context context) {
@@ -141,10 +142,14 @@
/** Updates the state, given the specified context */
void update(Context context) {
+ SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
Resources res = context.getResources();
DisplayMetrics dm = res.getDisplayMetrics();
mDisplayMetrics = dm;
+ // Debug mode
+ debugModeEnabled = settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false);
+
// Animations
animationPxMovementPerSecond =
res.getDimensionPixelSize(R.dimen.recents_animation_movement_in_dps_per_second);
@@ -168,7 +173,6 @@
searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height);
// Update the search widget id
- SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1);
// Task stack
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
index e554af7..b6895d1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
@@ -26,9 +26,11 @@
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
+import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.views.TaskStackView;
+import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
import com.android.systemui.recents.views.TaskViewTransform;
import java.lang.ref.WeakReference;
@@ -69,6 +71,7 @@
// Create a dummy task stack & compute the rect for the thumbnail to animate to
TaskStack stack = new TaskStack(context);
TaskStackView tsv = new TaskStackView(context, stack);
+ TaskStackViewLayoutAlgorithm algo = tsv.getStackAlgorithm();
Bundle replyData = new Bundle();
TaskViewTransform transform;
@@ -84,7 +87,7 @@
tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() -
systemInsets.top - systemInsets.bottom, 0, 0);
tsv.setStackScrollToInitialState();
- transform = tsv.getStackTransform(0, tsv.getStackScroll());
+ transform = algo.getStackTransform(0, tsv.getStackScroll());
transform.rect.offset(taskStackBounds.left, taskStackBounds.top);
replyData.putParcelable(AlternateRecentsComponent.KEY_SINGLE_TASK_STACK_RECT,
new Rect(transform.rect));
@@ -94,7 +97,7 @@
tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() -
systemInsets.top - systemInsets.bottom, 0, 0);
tsv.setStackScrollToInitialState();
- transform = tsv.getStackTransform(1, tsv.getStackScroll());
+ transform = algo.getStackTransform(1, tsv.getStackScroll());
transform.rect.offset(taskStackBounds.left, taskStackBounds.top);
replyData.putParcelable(AlternateRecentsComponent.KEY_TWO_TASK_STACK_RECT,
new Rect(transform.rect));
@@ -104,7 +107,7 @@
tsv.computeRects(taskStackBounds.width(), taskStackBounds.height() -
systemInsets.top - systemInsets.bottom, 0, 0);
tsv.setStackScrollToInitialState();
- transform = tsv.getStackTransform(2, tsv.getStackScroll());
+ transform = algo.getStackTransform(2, tsv.getStackScroll());
transform.rect.offset(taskStackBounds.left, taskStackBounds.top);
replyData.putParcelable(AlternateRecentsComponent.KEY_MULTIPLE_TASK_STACK_RECT,
new Rect(transform.rect));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
index c47f7d7..f7f86c4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/SystemServicesProxy.java
@@ -306,6 +306,13 @@
}
Drawable icon = info.loadIcon(mPm);
+ return getBadgedIcon(icon, userId);
+ }
+
+ /**
+ * Returns the given icon for a user, badging if necessary.
+ */
+ public Drawable getBadgedIcon(Drawable icon, int userId) {
if (userId != UserHandle.myUserId()) {
icon = mUm.getBadgedDrawableForUser(icon, new UserHandle(userId));
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
index 46e6ee9..6d6376f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Utilities.java
@@ -20,8 +20,26 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
/* Common code */
public class Utilities {
+
+ // Reflection methods for altering shadows
+ private static Method sPropertyMethod;
+ static {
+ try {
+ Class<?> c = Class.forName("android.view.GLES20Canvas");
+ sPropertyMethod = c.getDeclaredMethod("setProperty", String.class, String.class);
+ if (!sPropertyMethod.isAccessible()) sPropertyMethod.setAccessible(true);
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ }
+
/**
* Calculates a consistent animation duration (ms) for all animations depending on the movement
* of the object being animated.
@@ -50,20 +68,26 @@
}
/** Calculates the luminance-preserved greyscale of a given color. */
- private static int colorToGreyscale(int color) {
+ public static int colorToGreyscale(int color) {
return Math.round(0.2126f * Color.red(color) + 0.7152f * Color.green(color) +
0.0722f * Color.blue(color));
}
/** Returns the ideal color to draw on top of a specified background color. */
- public static int getIdealColorForBackgroundColor(int color, int lightRes, int darkRes) {
- int greyscale = colorToGreyscale(color);
+ public static int getIdealColorForBackgroundColorGreyscale(int greyscale, int lightRes,
+ int darkRes) {
return (greyscale < 128) ? lightRes : darkRes;
}
/** Returns the ideal drawable to draw on top of a specified background color. */
- public static Drawable getIdealResourceForBackgroundColor(int color, Drawable lightRes,
- Drawable darkRes) {
- int greyscale = colorToGreyscale(color);
+ public static Drawable getIdealResourceForBackgroundColorGreyscale(int greyscale,
+ Drawable lightRes,
+ Drawable darkRes) {
return (greyscale < 128) ? lightRes : darkRes;
}
+
+ /** Sets some private shadow properties. */
+ public static void setShadowProperty(String property, String value)
+ throws IllegalAccessException, InvocationTargetException {
+ sPropertyMethod.invoke(null, property, value);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
new file mode 100644
index 0000000..1344729
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/BitmapLruCache.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.model;
+
+import android.graphics.Bitmap;
+
+/**
+ * The Bitmap LRU cache.
+ */
+class BitmapLruCache extends KeyStoreLruCache<Bitmap> {
+ public BitmapLruCache(int cacheSize) {
+ super(cacheSize);
+ }
+
+ @Override
+ protected int computeSize(Bitmap b) {
+ // The cache size will be measured in kilobytes rather than number of items
+ return b.getAllocationByteCount() / 1024;
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
new file mode 100644
index 0000000..61d19da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/DrawableLruCache.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.model;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * The Drawable LRU cache.
+ */
+class DrawableLruCache extends KeyStoreLruCache<Drawable> {
+ public DrawableLruCache(int cacheSize) {
+ super(cacheSize);
+ }
+
+ @Override
+ protected int computeSize(Drawable d) {
+ // The cache size will be measured in kilobytes rather than number of items
+ // NOTE: this isn't actually correct, as the icon may be smaller
+ int maxBytes = (d.getIntrinsicWidth() * d.getIntrinsicHeight() * 4);
+ return maxBytes / 1024;
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
new file mode 100644
index 0000000..3ccca9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/KeyStoreLruCache.java
@@ -0,0 +1,112 @@
+/*
+ * 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.systemui.recents.model;
+
+import android.util.LruCache;
+
+import java.util.HashMap;
+
+/**
+ * An LRU cache that support querying the keys as well as values. By using the Task's key, we can
+ * prevent holding onto a reference to the Task resource data, while keeping the cache data in
+ * memory where necessary.
+ */
+public class KeyStoreLruCache<V> {
+ // We keep a set of keys that are associated with the LRU cache, so that we can find out
+ // information about the Task that was previously in the cache.
+ HashMap<Task.TaskKey, Task.TaskKey> mKeys = new HashMap<Task.TaskKey, Task.TaskKey>();
+ // The cache implementation
+ LruCache<Task.TaskKey, V> mCache;
+
+ public KeyStoreLruCache(int cacheSize) {
+ mCache = new LruCache<Task.TaskKey, V>(cacheSize) {
+ @Override
+ protected int sizeOf(Task.TaskKey t, V v) {
+ return computeSize(v);
+ }
+
+ @Override
+ protected void entryRemoved(boolean evicted, Task.TaskKey key, V oldV, V newV) {
+ mKeys.remove(key);
+ }
+ };
+ }
+
+ /** Computes the size of a value. */
+ protected int computeSize(V value) {
+ return 0;
+ }
+
+ /** Gets a specific entry in the cache. */
+ final V get(Task.TaskKey key) {
+ return mCache.get(key);
+ }
+
+ /**
+ * Returns the value only if the last active time of the key currently in the lru cache is
+ * greater than or equal to the last active time of the key specified.
+ */
+ final V getCheckLastActiveTime(Task.TaskKey key) {
+ Task.TaskKey lruKey = mKeys.get(key);
+ if (lruKey != null && (lruKey.lastActiveTime < key.lastActiveTime)) {
+ // The task has changed (been made active since the last time it was put into the
+ // LRU cache) so invalidate that item in the cache
+ remove(lruKey);
+ return null;
+ }
+ // Either the task does not exist in the cache, or the last active time is the same as
+ // the key specified
+ return mCache.get(key);
+ }
+
+ /** Gets the previous task key that matches the specified key. */
+ final Task.TaskKey getKey(Task.TaskKey key) {
+ return mKeys.get(key);
+ }
+
+ /** Puts an entry in the cache for a specific key. */
+ final void put(Task.TaskKey key, V value) {
+ mCache.put(key, value);
+ if (mKeys.containsKey(key)) {
+ mKeys.get(key).updateLastActiveTime(key.lastActiveTime);
+ } else {
+ mKeys.put(key, key);
+ }
+ }
+
+ /** Removes a cache entry for a specific key. */
+ final void remove(Task.TaskKey key) {
+ mCache.remove(key);
+ mKeys.remove(key);
+ }
+
+ /** Removes all the entries in the cache. */
+ final void evictAll() {
+ mCache.evictAll();
+ mKeys.clear();
+ }
+
+ /** Returns the size of the cache. */
+ final int size() {
+ return mCache.size();
+ }
+
+ /** Trims the cache to a specific size */
+ final void trimToSize(int cacheSize) {
+ mCache.resize(cacheSize);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/recents/RecentsPackageMonitor.java
rename to packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
index 04d1f1f..cbe39e3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.recents;
+package com.android.systemui.recents.model;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.os.Looper;
import com.android.internal.content.PackageMonitor;
+import com.android.systemui.recents.SystemServicesProxy;
import java.util.HashSet;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
rename to packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 07a6a56..15f4a76 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.recents;
+package com.android.systemui.recents.model;
import android.app.ActivityManager;
import android.content.ComponentCallbacks2;
@@ -27,11 +27,11 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
-import android.util.LruCache;
import android.util.Pair;
-import com.android.systemui.recents.model.SpaceNode;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.Console;
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.SystemServicesProxy;
import java.util.ArrayList;
import java.util.Collections;
@@ -204,8 +204,8 @@
final Task t = nextTaskData.first;
final boolean forceLoadTask = nextTaskData.second;
if (t != null) {
- Drawable loadIcon = mApplicationIconCache.get(t.key);
- Bitmap loadThumbnail = mThumbnailCache.get(t.key);
+ Drawable loadIcon = mApplicationIconCache.getCheckLastActiveTime(t.key);
+ Bitmap loadThumbnail = mThumbnailCache.getCheckLastActiveTime(t.key);
if (Console.Enabled) {
Console.log(Constants.Log.App.TaskDataLoader,
" [TaskResourceLoader|load]",
@@ -255,7 +255,7 @@
mMainThreadHandler.post(new Runnable() {
@Override
public void run() {
- t.notifyTaskDataLoaded(newThumbnail, newIcon, forceLoadTask);
+ t.notifyTaskDataLoaded(newThumbnail, newIcon);
}
});
}
@@ -282,40 +282,6 @@
}
}
-/**
- * The drawable cache. By using the Task's key, we can prevent holding onto a reference to the Task
- * resource data, while keeping the cache data in memory where necessary.
- */
-class DrawableLruCache extends LruCache<Task.TaskKey, Drawable> {
- public DrawableLruCache(int cacheSize) {
- super(cacheSize);
- }
-
- @Override
- protected int sizeOf(Task.TaskKey t, Drawable d) {
- // The cache size will be measured in kilobytes rather than number of items
- // NOTE: this isn't actually correct, as the icon may be smaller
- int maxBytes = (d.getIntrinsicWidth() * d.getIntrinsicHeight() * 4);
- return maxBytes / 1024;
- }
-}
-
-/**
- * The bitmap cache. By using the Task's key, we can prevent holding onto a reference to the Task
- * resource data, while keeping the cache data in memory where necessary.
- */
-class BitmapLruCache extends LruCache<Task.TaskKey, Bitmap> {
- public BitmapLruCache(int cacheSize) {
- super(cacheSize);
- }
-
- @Override
- protected int sizeOf(Task.TaskKey t, Bitmap bitmap) {
- // The cache size will be measured in kilobytes rather than number of items
- return bitmap.getAllocationByteCount() / 1024;
- }
-}
-
/* Recents task loader
* NOTE: We should not hold any references to a Context from a static instance */
public class RecentsTaskLoader {
@@ -417,12 +383,13 @@
}
/** Reload the set of recent tasks */
- SpaceNode reload(Context context, int preloadCount) {
+ public SpaceNode reload(Context context, int preloadCount) {
long t1 = System.currentTimeMillis();
if (Console.Enabled) {
Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|reload]");
}
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
Resources res = context.getResources();
ArrayList<Task> tasksToForceLoad = new ArrayList<Task>();
TaskStack stack = new TaskStack(context);
@@ -443,11 +410,12 @@
ActivityManager.TaskDescription av = t.taskDescription;
String activityLabel = null;
- BitmapDrawable activityIcon = null;
- int activityColor = 0;
+ Drawable activityIcon = null;
+ int activityColor = config.taskBarViewDefaultBackgroundColor;
if (av != null) {
activityLabel = (av.getLabel() != null ? av.getLabel() : ssp.getActivityLabel(info));
- activityIcon = (av.getIcon() != null) ? new BitmapDrawable(res, av.getIcon()) : null;
+ activityIcon = (av.getIcon() != null) ?
+ ssp.getBadgedIcon(new BitmapDrawable(res, av.getIcon()), t.userId) : null;
activityColor = av.getPrimaryColor();
} else {
activityLabel = ssp.getActivityLabel(info);
@@ -456,7 +424,7 @@
// Create a new task
Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, activityLabel,
- activityIcon, activityColor, t.userId);
+ activityIcon, activityColor, t.userId, t.lastActiveTime);
// Preload the specified number of apps
if (i >= (taskCount - preloadCount)) {
@@ -466,45 +434,44 @@
"i: " + i + " task: " + t.baseIntent.getComponent().getPackageName());
}
- // Load the icon (if possible and not the foremost task, from the cache)
- if (!isForemostTask) {
- task.applicationIcon = mApplicationIconCache.get(task.key);
- if (task.applicationIcon != null) {
- // Even though we get things from the cache, we should update them
- // if they've changed in the bg
- tasksToForceLoad.add(task);
- }
- }
+ // Load the icon from the cache if possible
+ task.applicationIcon = mApplicationIconCache.getCheckLastActiveTime(task.key);
if (task.applicationIcon == null) {
- task.applicationIcon = ssp.getActivityIcon(info, task.userId);
- if (task.applicationIcon != null) {
- mApplicationIconCache.put(task.key, task.applicationIcon);
+ if (isForemostTask) {
+ // We force loading the application icon for the foremost task
+ task.applicationIcon = ssp.getActivityIcon(info, task.userId);
+ if (task.applicationIcon != null) {
+ mApplicationIconCache.put(task.key, task.applicationIcon);
+ } else {
+ task.applicationIcon = mDefaultApplicationIcon;
+ }
} else {
- task.applicationIcon = mDefaultApplicationIcon;
+ // Either the task has updated, or we haven't cached any information for the
+ // task, so reload it
+ tasksToForceLoad.add(task);
}
}
// Load the thumbnail (if possible and not the foremost task, from the cache)
- if (!isForemostTask) {
- task.thumbnail = mThumbnailCache.get(task.key);
- if (task.thumbnail != null && !tasksToForceLoad.contains(task)) {
- // Even though we get things from the cache, we should update them if
- // they've changed in the bg
- tasksToForceLoad.add(task);
- }
- }
+ task.thumbnail = mThumbnailCache.getCheckLastActiveTime(task.key);
if (task.thumbnail == null) {
if (Console.Enabled) {
Console.log(Constants.Log.App.TaskDataLoader,
"[RecentsTaskLoader|loadingTaskThumbnail]");
}
-
- task.thumbnail = ssp.getTaskThumbnail(task.key.id);
- if (task.thumbnail != null) {
- task.thumbnail.setHasAlpha(false);
- mThumbnailCache.put(task.key, task.thumbnail);
+ if (isForemostTask) {
+ // We force loading the thumbnail icon for the foremost task
+ task.thumbnail = ssp.getTaskThumbnail(task.key.id);
+ if (task.thumbnail != null) {
+ task.thumbnail.setHasAlpha(false);
+ mThumbnailCache.put(task.key, task.thumbnail);
+ } else {
+ task.thumbnail = mDefaultThumbnail;
+ }
} else {
- task.thumbnail = mDefaultThumbnail;
+ // Either the task has updated, or we haven't cached any information for the
+ // task, so reload it
+ tasksToForceLoad.add(task);
}
}
}
@@ -536,7 +503,7 @@
return root;
}
- /** Acquires the task resource data from the pool. */
+ /** Acquires the task resource data directly from the pool. */
public void loadTaskData(Task t) {
Drawable applicationIcon = mApplicationIconCache.get(t.key);
Bitmap thumbnail = mThumbnailCache.get(t.key);
@@ -559,7 +526,7 @@
if (requiresLoad) {
mLoadQueue.addTask(t, false);
}
- t.notifyTaskDataLoaded(thumbnail, applicationIcon, false);
+ t.notifyTaskDataLoaded(thumbnail, applicationIcon);
}
/** Releases the task resource data back into the pool. */
@@ -613,7 +580,7 @@
* Handles signals from the system, trimming memory when requested to prevent us from running
* out of memory.
*/
- void onTrimMemory(int level) {
+ public void onTrimMemory(int level) {
if (Console.Enabled) {
Console.log(Constants.Log.App.Memory, "[RecentsTaskLoader|onTrimMemory]",
Console.trimMemoryLevelToString(level));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index f366ef0..abfb221 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -16,11 +16,10 @@
package com.android.systemui.recents.model;
-import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import com.android.systemui.recents.Utilities;
/**
@@ -30,7 +29,7 @@
/* Task callbacks */
public interface TaskCallbacks {
/* Notifies when a task has been bound */
- public void onTaskDataLoaded(boolean reloadingTaskData);
+ public void onTaskDataLoaded();
/* Notifies when a task has been unbound */
public void onTaskDataUnloaded();
}
@@ -40,11 +39,17 @@
public final int id;
public final Intent baseIntent;
public final int userId;
+ public long lastActiveTime;
- public TaskKey(int id, Intent intent, int userId) {
+ public TaskKey(int id, Intent intent, int userId, long lastActiveTime) {
this.id = id;
this.baseIntent = intent;
this.userId = userId;
+ this.lastActiveTime = lastActiveTime;
+ }
+
+ public void updateLastActiveTime(long lastActiveTime) {
+ this.lastActiveTime = lastActiveTime;
}
@Override
@@ -64,7 +69,8 @@
@Override
public String toString() {
return "Task.Key: " + id + ", "
- + "u" + userId + ", "
+ + "u: " + userId + ", "
+ + "lat: " + lastActiveTime + ", "
+ baseIntent.getComponent().getPackageName();
}
}
@@ -74,6 +80,7 @@
public Drawable activityIcon;
public String activityLabel;
public int colorPrimary;
+ public int colorPrimaryGreyscale;
public Bitmap thumbnail;
public boolean isActive;
public int userId;
@@ -85,11 +92,12 @@
}
public Task(int id, boolean isActive, Intent intent, String activityTitle,
- BitmapDrawable activityIcon, int colorPrimary, int userId) {
- this.key = new TaskKey(id, intent, userId);
+ Drawable activityIcon, int colorPrimary, int userId, long lastActiveTime) {
+ this.key = new TaskKey(id, intent, userId, lastActiveTime);
this.activityLabel = activityTitle;
this.activityIcon = activityIcon;
this.colorPrimary = colorPrimary;
+ this.colorPrimaryGreyscale = Utilities.colorToGreyscale(colorPrimary);
this.isActive = isActive;
this.userId = userId;
}
@@ -100,12 +108,11 @@
}
/** Notifies the callback listeners that this task has been loaded */
- public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon,
- boolean reloadingTaskData) {
+ public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) {
this.applicationIcon = applicationIcon;
this.thumbnail = thumbnail;
if (mCb != null) {
- mCb.onTaskDataLoaded(reloadingTaskData);
+ mCb.onTaskDataLoaded();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java
rename to packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
index cadfc56..6568b1a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FullScreenTransitionView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FullscreenTransitionOverlayView.java
@@ -23,12 +23,14 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Rect;
+import android.util.AttributeSet;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import com.android.systemui.R;
import com.android.systemui.recents.Console;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
@@ -38,9 +40,9 @@
* The full screen transition view that gets animated down from the full screen into a task
* thumbnail view.
*/
-public class FullScreenTransitionView extends FrameLayout {
+public class FullscreenTransitionOverlayView extends FrameLayout {
- /** The FullScreenTransitionView callbacks */
+ /** The FullscreenTransitionOverlayView callbacks */
public interface FullScreenTransitionViewCallbacks {
void onEnterAnimationComplete(boolean canceled);
}
@@ -56,20 +58,37 @@
boolean mIsAnimating;
AnimatorSet mEnterAnimation;
- public FullScreenTransitionView(Context context, FullScreenTransitionViewCallbacks cb) {
+ public FullscreenTransitionOverlayView(Context context) {
super(context);
+ }
+
+ public FullscreenTransitionOverlayView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FullscreenTransitionOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public FullscreenTransitionOverlayView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
mConfig = RecentsConfiguration.getInstance();
- mCb = cb;
- mScreenshotView = new ImageView(context);
- mScreenshotView.setScaleType(ImageView.ScaleType.FIT_XY);
- mScreenshotView.setLayoutParams(new FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
- addView(mScreenshotView);
setClipTop(getClipTop());
setClipBottom(getClipBottom());
setWillNotDraw(false);
}
+ @Override
+ protected void onFinishInflate() {
+ mScreenshotView = (ImageView) findViewById(R.id.image);
+ }
+
+ /** Sets the callbacks */
+ public void setCallbacks(FullScreenTransitionViewCallbacks cb) {
+ mCb = cb;
+ }
+
/** Sets the top clip */
public void setClipTop(int clip) {
mClipRect.top = clip;
@@ -154,10 +173,12 @@
}
// Calculate the bottom clip
- float scale = (float) ctx.taskRect.width() / getMeasuredWidth();
- int translationY = -mConfig.systemInsets.top + ctx.stackRectSansPeek.top +
- ctx.transform.translationY;
- int clipBottom = mConfig.systemInsets.top + (int) (ctx.taskRect.height() / scale);
+ Rect taskRect = ctx.taskRect;
+ float scale = (float) taskRect.width() / getMeasuredWidth();
+ float scaleYOffset = ((1f - scale) * getMeasuredHeight()) / 2;
+ float scaledTopInset = (int) (scale * mConfig.systemInsets.top);
+ int translationY = (int) -scaleYOffset + (int) (mConfig.systemInsets.top - scaledTopInset) + taskRect.top;
+ int clipBottom = mConfig.systemInsets.top + (int) (taskRect.height() / scale);
// Enable the HW Layers on the screenshot view
mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 7248758..e7eac67 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -24,10 +24,12 @@
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.Rect;
import android.net.Uri;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowInsets;
@@ -35,8 +37,8 @@
import com.android.systemui.recents.Console;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsPackageMonitor;
-import com.android.systemui.recents.RecentsTaskLoader;
+import com.android.systemui.recents.model.RecentsPackageMonitor;
+import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.SpaceNode;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
@@ -44,7 +46,6 @@
import java.util.ArrayList;
import java.util.Set;
-
/**
* This view is the the top level layout that contains TaskStacks (which are laid out according
* to their SpaceNode bounds.
@@ -55,11 +56,13 @@
/** The RecentsView callbacks */
public interface RecentsViewCallbacks {
public void onTaskLaunching();
- public void onExitAnimationTriggered();
+ public void onLastTaskRemoved();
+ public void onExitToHomeAnimationTriggered();
}
RecentsConfiguration mConfig;
LayoutInflater mInflater;
+ Paint mDebugModePaint;
// The space partitioning root of this container
SpaceNode mBSP;
@@ -72,6 +75,18 @@
public RecentsView(Context context) {
super(context);
+ }
+
+ public RecentsView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
mConfig = RecentsConfiguration.getInstance();
mInflater = LayoutInflater.from(context);
}
@@ -95,6 +110,15 @@
addView(stackView);
mHasTasks |= (stack.getTaskCount() > 0);
}
+
+ // Enable debug mode drawing
+ if (mConfig.debugModeEnabled) {
+ mDebugModePaint = new Paint();
+ mDebugModePaint.setColor(0xFFff0000);
+ mDebugModePaint.setStyle(Paint.Style.STROKE);
+ mDebugModePaint.setStrokeWidth(5f);
+ setWillNotDraw(false);
+ }
}
/** Launches the focused task from the first stack if possible */
@@ -178,14 +202,12 @@
// animations are started.
ctx.postAnimationTrigger.increment();
- if (Constants.DebugFlags.App.EnableHomeTransition) {
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child instanceof TaskStackView) {
- TaskStackView stackView = (TaskStackView) child;
- stackView.startExitToHomeAnimation(ctx);
- }
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (child instanceof TaskStackView) {
+ TaskStackView stackView = (TaskStackView) child;
+ stackView.startExitToHomeAnimation(ctx);
}
}
@@ -194,7 +216,7 @@
ctx.postAnimationTrigger.decrement();
// Notify of the exit animation
- mCb.onExitAnimationTriggered();
+ mCb.onExitToHomeAnimationTriggered();
}
/** Adds the search bar */
@@ -310,6 +332,29 @@
}
}
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ // Debug mode drawing
+ if (mConfig.debugModeEnabled) {
+ canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mDebugModePaint);
+ }
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ if (Console.Enabled) {
+ Console.log(Constants.Log.UI.MeasureAndLayout,
+ "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen);
+ }
+
+ // Update the configuration with the latest system insets and trigger a relayout
+ mConfig.updateSystemInsets(insets.getSystemWindowInsets());
+ requestLayout();
+
+ return insets.consumeSystemWindowInsets(false, false, false, true);
+ }
+
/** Notifies each task view of the user interaction. */
public void onUserInteraction() {
// Get the first stack view
@@ -342,29 +387,6 @@
}
}
- @Override
- protected void dispatchDraw(Canvas canvas) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.Draw, "[RecentsView|dispatchDraw]", "",
- Console.AnsiPurple);
- }
- super.dispatchDraw(canvas);
- }
-
- @Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.MeasureAndLayout,
- "[RecentsView|fitSystemWindows]", "insets: " + insets, Console.AnsiGreen);
- }
-
- // Update the configuration with the latest system insets and trigger a relayout
- mConfig.updateSystemInsets(insets.getSystemWindowInsets());
- requestLayout();
-
- return insets.consumeSystemWindowInsets(false, false, false, true);
- }
-
/** Unfilters any filtered stacks */
public boolean unfilterFilteredStacks() {
if (mBSP != null) {
@@ -403,11 +425,13 @@
// and then offset to the expected transform rect, but bound this to just
// outside the display rect (to ensure we don't animate from too far away)
sourceView = stackView;
- transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
+ transform = stackView.getStackAlgorithm().getStackTransform(stack.indexOfTask(task),
+ stackScroll);
offsetX = transform.rect.left;
offsetY = Math.min(transform.rect.top, mConfig.displayRect.height());
} else {
- transform = stackView.getStackTransform(stack.indexOfTask(task), stackScroll);
+ transform = stackView.getStackAlgorithm().getStackTransform(stack.indexOfTask(task),
+ stackScroll);
}
// Compute the thumbnail to scale up from
@@ -509,6 +533,39 @@
isDocument);
}
+ @Override
+ public void onLastTaskRemoved() {
+ mCb.onLastTaskRemoved();
+ }
+
+ @Override
+ public void onTaskStackFilterTriggered() {
+ // Hide the search bar
+ if (mSearchBar != null) {
+ mSearchBar.animate()
+ .alpha(0f)
+ .setStartDelay(0)
+ .setInterpolator(mConfig.fastOutSlowInInterpolator)
+ .setDuration(mConfig.filteringCurrentViewsAnimDuration)
+ .withLayer()
+ .start();
+ }
+ }
+
+ @Override
+ public void onTaskStackUnfilterTriggered() {
+ // Show the search bar
+ if (mSearchBar != null) {
+ mSearchBar.animate()
+ .alpha(1f)
+ .setStartDelay(0)
+ .setInterpolator(mConfig.fastOutSlowInInterpolator)
+ .setDuration(mConfig.filteringNewViewsAnimDuration)
+ .withLayer()
+ .start();
+ }
+ }
+
/**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
new file mode 100644
index 0000000..5b17b41
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -0,0 +1,109 @@
+/*
+ * 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.systemui.recents.views;
+
+import android.app.Activity;
+import android.view.View;
+import com.android.systemui.R;
+import com.android.systemui.recents.RecentsConfiguration;
+
+/** Manages the scrims for the various system bars. */
+public class SystemBarScrimViews {
+
+ RecentsConfiguration mConfig;
+
+ View mStatusBarScrimView;
+ View mNavBarScrimView;
+
+ boolean mHasNavBarScrim;
+ boolean mShouldAnimateStatusBarScrim;
+ boolean mHasStatusBarScrim;
+ boolean mShouldAnimateNavBarScrim;
+
+ public SystemBarScrimViews(Activity activity, RecentsConfiguration config) {
+ mConfig = config;
+ mStatusBarScrimView = activity.findViewById(R.id.status_bar_scrim);
+ mNavBarScrimView = activity.findViewById(R.id.nav_bar_scrim);
+ }
+
+ /**
+ * Prepares the scrim views for animating when entering Recents. This will be called before
+ * the first draw.
+ */
+ public void prepareEnterRecentsAnimation() {
+ mHasNavBarScrim = mConfig.hasNavBarScrim();
+ mShouldAnimateNavBarScrim = mConfig.shouldAnimateNavBarScrim();
+ mHasStatusBarScrim = mConfig.hasStatusBarScrim();
+ mShouldAnimateStatusBarScrim = mConfig.shouldAnimateStatusBarScrim();
+
+ mNavBarScrimView.setVisibility(mHasNavBarScrim && !mShouldAnimateNavBarScrim ?
+ View.VISIBLE : View.INVISIBLE);
+ mStatusBarScrimView.setVisibility(mHasStatusBarScrim && !mShouldAnimateStatusBarScrim ?
+ View.VISIBLE : View.INVISIBLE);
+ }
+
+ /**
+ * Starts animating the scrim views when entering Recents.
+ */
+ public void startEnterRecentsAnimation() {
+ if (mHasStatusBarScrim && mShouldAnimateStatusBarScrim) {
+ mStatusBarScrimView.setTranslationY(-mStatusBarScrimView.getMeasuredHeight());
+ mStatusBarScrimView.animate()
+ .translationY(0)
+ .setStartDelay(mConfig.taskBarEnterAnimDelay)
+ .setDuration(mConfig.navBarScrimEnterDuration)
+ .setInterpolator(mConfig.quintOutInterpolator)
+ .withStartAction(new Runnable() {
+ @Override
+ public void run() {
+ mStatusBarScrimView.setVisibility(View.VISIBLE);
+ }
+ })
+ .start();
+ }
+ if (mHasNavBarScrim && mShouldAnimateNavBarScrim) {
+ mNavBarScrimView.setTranslationY(mNavBarScrimView.getMeasuredHeight());
+ mNavBarScrimView.animate()
+ .translationY(0)
+ .setStartDelay(mConfig.taskBarEnterAnimDelay)
+ .setDuration(mConfig.navBarScrimEnterDuration)
+ .setInterpolator(mConfig.quintOutInterpolator)
+ .withStartAction(new Runnable() {
+ @Override
+ public void run() {
+ mNavBarScrimView.setVisibility(View.VISIBLE);
+ }
+ })
+ .start();
+ }
+ }
+
+ /**
+ * Starts animating the scrim views when leaving Recents (either via launching a task, or
+ * going home).
+ */
+ public void startExitRecentsAnimation() {
+ if (mHasNavBarScrim && mShouldAnimateNavBarScrim) {
+ mNavBarScrimView.animate()
+ .translationY(mNavBarScrimView.getMeasuredHeight())
+ .setStartDelay(0)
+ .setDuration(mConfig.taskBarExitAnimDuration)
+ .setInterpolator(mConfig.fastOutSlowInInterpolator)
+ .start();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
index 1ef58ad..2c0dea3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskBarView.java
@@ -25,6 +25,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
@@ -88,6 +89,15 @@
}
@Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // We ignore taps on the task bar except on the filter and dismiss buttons
+ if (!Constants.DebugFlags.App.EnableTaskBarTouchEvents) return true;
+ if (mConfig.debugModeEnabled) return true;
+
+ return super.onTouchEvent(event);
+ }
+
+ @Override
protected void onFinishInflate() {
// Initialize the icon and description views
mApplicationIcon = (ImageView) findViewById(R.id.application_icon);
@@ -139,7 +149,7 @@
}
/** Binds the bar view to the task */
- void rebindToTask(Task t, boolean animate) {
+ void rebindToTask(Task t) {
mTask = t;
// If an activity icon is defined, then we use that as the primary icon to show in the bar,
// otherwise, we fall back to the application icon
@@ -150,15 +160,12 @@
}
mActivityDescription.setText(t.activityLabel);
// Try and apply the system ui tint
- int tint = t.colorPrimary;
- if (!Constants.DebugFlags.App.EnableTaskBarThemeColors || tint == 0) {
- tint = mConfig.taskBarViewDefaultBackgroundColor;
- }
- setBackgroundColor(tint);
- mActivityDescription.setTextColor(Utilities.getIdealColorForBackgroundColor(tint,
- mConfig.taskBarViewLightTextColor, mConfig.taskBarViewDarkTextColor));
- mDismissButton.setImageDrawable(Utilities.getIdealResourceForBackgroundColor(tint,
- mLightDismissDrawable, mDarkDismissDrawable));
+ setBackgroundColor(t.colorPrimary);
+ mActivityDescription.setTextColor(Utilities.getIdealColorForBackgroundColorGreyscale(
+ t.colorPrimaryGreyscale, mConfig.taskBarViewLightTextColor,
+ mConfig.taskBarViewDarkTextColor));
+ mDismissButton.setImageDrawable(Utilities.getIdealResourceForBackgroundColorGreyscale(
+ t.colorPrimaryGreyscale, mLightDismissDrawable, mDarkDismissDrawable));
}
/** Unbinds the bar view from the task */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 5380116..29eaa9e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -20,7 +20,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
-import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Canvas;
@@ -28,10 +27,7 @@
import android.graphics.Region;
import android.view.LayoutInflater;
import android.view.MotionEvent;
-import android.view.VelocityTracker;
import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewParent;
import android.widget.FrameLayout;
import android.widget.OverScroller;
import com.android.systemui.R;
@@ -39,15 +35,14 @@
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.DozeTrigger;
import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsPackageMonitor;
-import com.android.systemui.recents.RecentsTaskLoader;
import com.android.systemui.recents.ReferenceCountedTrigger;
import com.android.systemui.recents.Utilities;
+import com.android.systemui.recents.model.RecentsPackageMonitor;
+import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.Set;
@@ -61,23 +56,22 @@
public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
public void onTaskAppInfoLaunched(Task t);
public void onTaskRemoved(Task t);
+ public void onLastTaskRemoved();
+ public void onTaskStackFilterTriggered();
+ public void onTaskStackUnfilterTriggered();
}
RecentsConfiguration mConfig;
TaskStack mStack;
+ TaskStackViewLayoutAlgorithm mStackAlgorithm;
+ TaskStackViewFilterAlgorithm mFilterAlgorithm;
TaskStackViewTouchHandler mTouchHandler;
TaskStackViewCallbacks mCb;
ViewPool<TaskView, Task> mViewPool;
ArrayList<TaskViewTransform> mTaskTransforms = new ArrayList<TaskViewTransform>();
DozeTrigger mUIDozeTrigger;
- // The various rects that define the stack view
- Rect mRect = new Rect();
- Rect mStackRect = new Rect();
- Rect mStackRectSansPeek = new Rect();
- Rect mTaskRect = new Rect();
-
// The virtual stack scroll that we use for the card layout
int mStackScroll;
int mMinScroll;
@@ -118,6 +112,8 @@
mTouchHandler = new TaskStackViewTouchHandler(context, this);
mViewPool = new ViewPool<TaskView, Task>(context, this);
mInflater = LayoutInflater.from(context);
+ mStackAlgorithm = new TaskStackViewLayoutAlgorithm(mConfig);
+ mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool);
mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() {
@Override
public void run() {
@@ -173,7 +169,7 @@
"[TaskStackView|requestSynchronize]", "" + duration + "ms", Console.AnsiYellow);
}
if (!mStackViewsDirty) {
- invalidate(mStackRect);
+ invalidate(mStackAlgorithm.mStackRect);
}
if (mAwaitingFirstLayout) {
// Skip the animation if we are awaiting first layout
@@ -185,7 +181,7 @@
}
/** Finds the child view given a specific task */
- private TaskView getChildViewForTask(Task t) {
+ TaskView getChildViewForTask(Task t) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskView tv = (TaskView) getChildAt(i);
@@ -196,65 +192,9 @@
return null;
}
- /** Update/get the transform (creates a new TaskViewTransform) */
- public TaskViewTransform getStackTransform(int indexInStack, int stackScroll) {
- TaskViewTransform transform = new TaskViewTransform();
- return getStackTransform(indexInStack, stackScroll, transform);
- }
-
- /** Update/get the transform */
- public TaskViewTransform getStackTransform(int indexInStack, int stackScroll,
- TaskViewTransform transformOut) {
- // Return early if we have an invalid index
- if (indexInStack < 0) {
- transformOut.reset();
- return transformOut;
- }
-
- // Map the items to an continuous position relative to the specified scroll
- int numPeekCards = Constants.Values.TaskStackView.StackPeekNumCards;
- float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
- float peekHeight = Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
- float t = ((indexInStack * overlapHeight) - stackScroll) / overlapHeight;
- float boundedT = Math.max(t, -(numPeekCards + 1));
-
- // Set the scale relative to its position
- int numFrontScaledCards = 3;
- float minScale = Constants.Values.TaskStackView.StackPeekMinScale;
- float scaleRange = 1f - minScale;
- float scaleInc = scaleRange / (numPeekCards + numFrontScaledCards);
- float scale = Math.max(minScale, Math.min(1f, minScale +
- ((boundedT + (numPeekCards + 1)) * scaleInc)));
- float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2;
- transformOut.scale = scale;
-
- // Set the y translation
- if (boundedT < 0f) {
- transformOut.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
- numPeekCards) * peekHeight - scaleYOffset);
- } else {
- transformOut.translationY = (int) (boundedT * overlapHeight - scaleYOffset);
- }
-
- // Set the z translation
- int minZ = mConfig.taskViewTranslationZMinPx;
- int incZ = mConfig.taskViewTranslationZIncrementPx;
- transformOut.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ));
-
- // Set the alphas
- transformOut.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f;
-
- // Update the rect and visibility
- transformOut.rect.set(mTaskRect);
- if (t < -(numPeekCards + 1)) {
- transformOut.visible = false;
- } else {
- transformOut.rect.offset(0, transformOut.translationY);
- Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
- transformOut.visible = Rect.intersects(mRect, transformOut.rect);
- }
- transformOut.t = t;
- return transformOut;
+ /** Returns the stack algorithm for this task stack. */
+ public TaskStackViewLayoutAlgorithm getStackAlgorithm() {
+ return mStackAlgorithm;
}
/**
@@ -285,7 +225,8 @@
// Update the stack transforms
for (int i = 0; i < taskCount; i++) {
- TaskViewTransform transform = getStackTransform(i, stackScroll, taskTransforms.get(i));
+ TaskViewTransform transform = mStackAlgorithm.getStackTransform(i, stackScroll,
+ taskTransforms.get(i));
if (transform.visible) {
if (firstVisibleIndex < 0) {
firstVisibleIndex = i;
@@ -294,7 +235,8 @@
}
if (boundTranslationsToRect) {
- transform.translationY = Math.min(transform.translationY, mRect.bottom);
+ transform.translationY = Math.min(transform.translationY,
+ mStackAlgorithm.mRect.bottom);
}
}
if (visibleRangeOut != null) {
@@ -351,7 +293,7 @@
int fromIndex = (transform.t < 0) ? (visibleRange[0] - 1) :
(visibleRange[1] + 1);
tv.updateViewPropertiesToTaskTransform(
- getStackTransform(fromIndex, stackScroll), 0);
+ mStackAlgorithm.getStackTransform(fromIndex, stackScroll), 0);
}
}
} else {
@@ -389,29 +331,24 @@
/** Sets the current stack scroll */
public void setStackScroll(int value) {
mStackScroll = value;
+ mUIDozeTrigger.poke();
requestSynchronizeStackViewsWithModel();
}
/** Sets the current stack scroll without synchronizing the stack view with the model */
public void setStackScrollRaw(int value) {
mStackScroll = value;
+ mUIDozeTrigger.poke();
}
/** Sets the current stack scroll to the initial state when you first enter recents */
public void setStackScrollToInitialState() {
- if (mStack.getTaskCount() > 2) {
- int initialScroll = mMaxScroll - mTaskRect.height() / 2;
- setStackScroll(initialScroll);
- } else {
- setStackScroll(mMaxScroll);
- }
+ setStackScroll(getInitialStackScroll());
}
-
- /**
- * Returns the scroll to such that the task transform at that index will have t=0. (If the scroll
- * is not bounded)
- */
- int getStackScrollForTaskIndex(int i) {
- int taskHeight = mTaskRect.height();
- return (int) (i * Constants.Values.TaskStackView.StackOverlapPct * taskHeight);
+ /** Computes the initial stack scroll for the stack. */
+ int getInitialStackScroll() {
+ if (mStack.getTaskCount() > 2) {
+ return mMaxScroll - mStackAlgorithm.mTaskRect.height() / 2;
+ }
+ return mMaxScroll;
}
/** Gets the current stack scroll */
@@ -525,29 +462,12 @@
return getScrollAmountOutOfBounds(getStackScroll()) != 0;
}
- /** Returns whether the task view is in the stack bounds or not */
- boolean isTaskInStackBounds(TaskView tv) {
- Rect r = new Rect();
- tv.getHitRect(r);
- return r.bottom <= mRect.bottom;
- }
-
/** Updates the min and max virtual scroll bounds */
void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
// Compute the min and max scroll values
- int numTasks = Math.max(1, mStack.getTaskCount());
- int taskHeight = mTaskRect.height();
- int stackHeight = mStackRectSansPeek.height();
- int maxScrollHeight = taskHeight + (int) ((numTasks - 1) *
- Constants.Values.TaskStackView.StackOverlapPct * taskHeight);
-
- if (numTasks <= 1) {
- // If there is only one task, then center the task in the stack rect (sans peek)
- mMinScroll = mMaxScroll = -(stackHeight - taskHeight) / 2;
- } else {
- mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
- mMaxScroll = maxScrollHeight - stackHeight;
- }
+ mStackAlgorithm.computeMinMaxScroll(mStack.getTaskCount());
+ mMinScroll = mStackAlgorithm.mMinScroll;
+ mMaxScroll = mStackAlgorithm.mMaxScroll;
// Debug logging
if (Constants.Log.UI.MeasureAndLayout) {
@@ -611,7 +531,7 @@
if (scrollToNewPosition) {
// Scroll the view into position
int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll,
- getStackScrollForTaskIndex(taskIndex)));
+ mStackAlgorithm.getStackScrollForTaskIndex(taskIndex)));
animateScroll(getStackScroll(), newScroll, postScrollRunnable);
} else {
@@ -665,19 +585,6 @@
}
@Override
- public void computeScroll() {
- if (mScroller.computeScrollOffset()) {
- setStackScroll(mScroller.getCurrY());
- invalidate(mStackRect);
-
- // If we just finished scrolling, then disable the hw layers
- if (mScroller.isFinished()) {
- decHwLayersRefCount("finishedFlingScroll");
- }
- }
- }
-
- @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mTouchHandler.onInterceptTouchEvent(ev);
}
@@ -688,6 +595,19 @@
}
@Override
+ public void computeScroll() {
+ if (mScroller.computeScrollOffset()) {
+ setStackScroll(mScroller.getCurrY());
+ invalidate(mStackAlgorithm.mStackRect);
+
+ // If we just finished scrolling, then disable the hw layers
+ if (mScroller.isFinished()) {
+ decHwLayersRefCount("finishedFlingScroll");
+ }
+ }
+ }
+
+ @Override
public void dispatchDraw(Canvas canvas) {
if (Console.Enabled) {
Console.log(Constants.Log.UI.Draw, "[TaskStackView|dispatchDraw]", "",
@@ -740,34 +660,8 @@
/** Computes the stack and task rects */
public void computeRects(int width, int height, int insetLeft, int insetBottom) {
- // Note: We let the stack view be the full height because we want the cards to go under the
- // navigation bar if possible. However, the stack rects which we use to calculate
- // max scroll, etc. need to take the nav bar into account
-
- // Compute the stack rects
- mRect.set(0, 0, width, height);
- mStackRect.set(mRect);
- mStackRect.left += insetLeft;
- mStackRect.bottom -= insetBottom;
-
- int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
- int heightPadding = mConfig.taskStackTopPaddingPx;
- if (Constants.DebugFlags.App.EnableSearchLayout) {
- mStackRect.top += heightPadding;
- mStackRect.left += widthPadding;
- mStackRect.right -= widthPadding;
- mStackRect.bottom -= heightPadding;
- } else {
- mStackRect.inset(widthPadding, heightPadding);
- }
- mStackRectSansPeek.set(mStackRect);
- mStackRectSansPeek.top += Constants.Values.TaskStackView.StackPeekHeightPct * mStackRect.height();
-
- // Compute the task rect
- int size = mStackRect.width();
- int left = mStackRect.left + (mStackRect.width() - size) / 2;
- mTaskRect.set(left, mStackRectSansPeek.top,
- left + size, mStackRectSansPeek.top + size);
+ // Compute the rects in the stack algorithm
+ mStackAlgorithm.computeRects(width, height, insetLeft, insetBottom);
// Update the scroll bounds
updateMinMaxScroll(false);
@@ -795,10 +689,10 @@
// Debug logging
if (Constants.Log.UI.MeasureAndLayout) {
- Console.log(" [TaskStack|fullRect] " + mRect);
- Console.log(" [TaskStack|stackRect] " + mStackRect);
- Console.log(" [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
- Console.log(" [TaskStack|taskRect] " + mTaskRect);
+ Console.log(" [TaskStack|fullRect] " + mStackAlgorithm.mRect);
+ Console.log(" [TaskStack|stackRect] " + mStackAlgorithm.mStackRect);
+ Console.log(" [TaskStack|stackRectSansPeek] " + mStackAlgorithm.mStackRectSansPeek);
+ Console.log(" [TaskStack|taskRect] " + mStackAlgorithm.mTaskRect);
}
// If this is the first layout, then scroll to the front of the stack and synchronize the
@@ -813,8 +707,8 @@
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskView t = (TaskView) getChildAt(i);
- t.measure(MeasureSpec.makeMeasureSpec(mTaskRect.width(), MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(mTaskRect.height(), MeasureSpec.EXACTLY));
+ t.measure(MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height(), MeasureSpec.EXACTLY));
}
setMeasuredDimension(width, height);
@@ -834,18 +728,19 @@
// Debug logging
if (Constants.Log.UI.MeasureAndLayout) {
- Console.log(" [TaskStack|fullRect] " + mRect);
- Console.log(" [TaskStack|stackRect] " + mStackRect);
- Console.log(" [TaskStack|stackRectSansPeek] " + mStackRectSansPeek);
- Console.log(" [TaskStack|taskRect] " + mTaskRect);
+ Console.log(" [TaskStack|fullRect] " + mStackAlgorithm.mRect);
+ Console.log(" [TaskStack|stackRect] " + mStackAlgorithm.mStackRect);
+ Console.log(" [TaskStack|stackRectSansPeek] " + mStackAlgorithm.mStackRectSansPeek);
+ Console.log(" [TaskStack|taskRect] " + mStackAlgorithm.mTaskRect);
}
// Layout each of the children
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskView t = (TaskView) getChildAt(i);
- t.layout(mTaskRect.left, mStackRectSansPeek.top,
- mTaskRect.right, mStackRectSansPeek.top + mTaskRect.height());
+ t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
+ mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
+ mStackAlgorithm.mTaskRect.height());
}
if (mAwaitingFirstLayout) {
@@ -856,12 +751,13 @@
mUIDozeTrigger.startDozing();
// Prepare the first view for its enter animation
- int offsetTopAlign = -mTaskRect.top;
- int offscreenY = mRect.bottom - (mTaskRect.top - mRect.top);
+ int offsetTopAlign = -mStackAlgorithm.mTaskRect.top;
+ int offscreenY = mStackAlgorithm.mRect.bottom -
+ (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top);
for (int i = childCount - 1; i >= 0; i--) {
TaskView tv = (TaskView) getChildAt(i);
tv.prepareEnterRecentsAnimation((i == (getChildCount() - 1)), offsetTopAlign,
- offscreenY, mTaskRect);
+ offscreenY, mStackAlgorithm.mTaskRect);
}
// If the enter animation started already and we haven't completed a layout yet, do the
@@ -889,17 +785,18 @@
}
// Animate all the task views into view
- ctx.taskRect = mTaskRect;
- ctx.stackRectSansPeek = mStackRectSansPeek;
+ TaskViewTransform transform = mStackAlgorithm.getStackTransform(mStack.getTaskCount() - 1,
+ getInitialStackScroll());
+ ctx.taskRect = transform.rect;
+ ctx.stackRectSansPeek = mStackAlgorithm.mStackRectSansPeek;
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
TaskView tv = (TaskView) getChildAt(i);
- TaskViewTransform transform = getStackTransform(mStack.indexOfTask(tv.getTask()),
- getStackScroll());
ctx.stackViewIndex = i;
ctx.stackViewCount = childCount;
ctx.isFrontMost = (i == (getChildCount() - 1));
- ctx.transform = transform;
+ ctx.transform = mStackAlgorithm.getStackTransform(
+ mStack.indexOfTask(tv.getTask()), getStackScroll());
tv.startEnterRecentsAnimation(ctx);
}
}
@@ -907,7 +804,8 @@
/** Requests this task stacks to start it's exit-recents animation. */
public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
// Animate all the task views into view
- ctx.offscreenTranslationY = mRect.bottom - (mTaskRect.top - mRect.top);
+ ctx.offscreenTranslationY = mStackAlgorithm.mRect.bottom -
+ (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
TaskView tv = (TaskView) getChildAt(i);
@@ -930,11 +828,13 @@
/** Pokes the dozer on user interaction. */
void onUserInteraction() {
- // If the dozer is not running, then either we have not yet laid out, or it has already
- // fallen asleep, so just let it rest.
- if (mUIDozeTrigger.isDozing()) {
- mUIDozeTrigger.poke();
- }
+ // Poke the doze trigger if it is dozing
+ mUIDozeTrigger.poke();
+ }
+
+ /** Disables handling touch on this task view. */
+ void setTouchOnTaskView(TaskView tv, boolean enabled) {
+ tv.setOnClickListener(enabled ? this : null);
}
/**** TaskStackCallbacks Implementation ****/
@@ -958,7 +858,7 @@
// Update the min/max scroll and animate other task views into their new positions
updateMinMaxScroll(true);
- int movement = (int) (Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height());
+ int movement = (int) mStackAlgorithm.getTaskOverlapHeight();
requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
// If there are no remaining tasks, then either unfilter the current stack, or just close
@@ -970,148 +870,11 @@
shouldFinishActivity = (mStack.getTaskCount() == 0);
}
if (shouldFinishActivity) {
- Activity activity = (Activity) getContext();
- activity.finish();
+ mCb.onLastTaskRemoved();
}
}
}
- /**
- * Creates the animations for all the children views that need to be removed or to move views
- * to their un/filtered position when we are un/filtering a stack, and returns the duration
- * for these animations.
- */
- int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks,
- ArrayList<TaskViewTransform> curTaskTransforms,
- ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms,
- HashMap<TaskView, TaskViewTransform> childViewTransformsOut,
- ArrayList<TaskView> childrenToRemoveOut) {
- // Animate all of the existing views out of view (if they are not in the visible range in
- // the new stack) or to their final positions in the new stack
- int offset = 0;
- int movement = 0;
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- TaskView tv = (TaskView) getChildAt(i);
- Task task = tv.getTask();
- int taskIndex = tasks.indexOf(task);
- TaskViewTransform toTransform;
-
- // If the view is no longer visible, then we should just animate it out
- boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible;
- if (willBeInvisible) {
- if (taskIndex < 0) {
- toTransform = curTaskTransforms.get(curTasks.indexOf(task));
- } else {
- toTransform = new TaskViewTransform(taskTransforms.get(taskIndex));
- }
- tv.prepareTaskTransformForFilterTaskVisible(toTransform);
- childrenToRemoveOut.add(tv);
- } else {
- toTransform = taskTransforms.get(taskIndex);
- // Use the movement of the visible views to calculate the duration of the animation
- movement = Math.max(movement, Math.abs(toTransform.translationY -
- (int) tv.getTranslationY()));
- }
-
- toTransform.startDelay = offset * Constants.Values.TaskStackView.FilterStartDelay;
- childViewTransformsOut.put(tv, toTransform);
- offset++;
- }
- return mConfig.filteringCurrentViewsAnimDuration;
- }
-
- /**
- * Creates the animations for all the children views that need to be animated in when we are
- * un/filtering a stack, and returns the duration for these animations.
- */
- int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks,
- ArrayList<TaskViewTransform> taskTransforms,
- HashMap<TaskView, TaskViewTransform> childViewTransformsOut) {
- int offset = 0;
- int movement = 0;
- int taskCount = tasks.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- Task task = tasks.get(i);
- TaskViewTransform toTransform = taskTransforms.get(i);
- if (toTransform.visible) {
- TaskView tv = getChildViewForTask(task);
- if (tv == null) {
- // For views that are not already visible, animate them in
- tv = mViewPool.pickUpViewFromPool(task, task);
-
- // Compose a new transform to fade and slide the new task in
- TaskViewTransform fromTransform = new TaskViewTransform(toTransform);
- tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
- tv.updateViewPropertiesToTaskTransform(fromTransform, 0);
-
- toTransform.startDelay = offset * Constants.Values.TaskStackView.FilterStartDelay;
- childViewTransformsOut.put(tv, toTransform);
-
- // Use the movement of the new views to calculate the duration of the animation
- movement = Math.max(movement,
- Math.abs(toTransform.translationY - fromTransform.translationY));
- offset++;
- }
- }
- }
- return mConfig.filteringNewViewsAnimDuration;
- }
-
- /** Orchestrates the animations of the current child views and any new views. */
- void doFilteringAnimation(ArrayList<Task> curTasks,
- ArrayList<TaskViewTransform> curTaskTransforms,
- final ArrayList<Task> tasks,
- final ArrayList<TaskViewTransform> taskTransforms) {
- // Calculate the transforms to animate out all the existing views if they are not in the
- // new visible range (or to their final positions in the stack if they are)
- final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>();
- final HashMap<TaskView, TaskViewTransform> childViewTransforms =
- new HashMap<TaskView, TaskViewTransform>();
- int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks,
- taskTransforms, childViewTransforms, childrenToRemove);
-
- // If all the current views are in the visible range of the new stack, then don't wait for
- // views to animate out and animate all the new views into their place
- final boolean unifyNewViewAnimation = childrenToRemove.isEmpty();
- if (unifyNewViewAnimation) {
- int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms,
- childViewTransforms);
- duration = Math.max(duration, inDuration);
- }
-
- // Animate all the views to their final transforms
- for (final TaskView tv : childViewTransforms.keySet()) {
- TaskViewTransform t = childViewTransforms.get(tv);
- tv.animate().cancel();
- tv.animate()
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- childViewTransforms.remove(tv);
- if (childViewTransforms.isEmpty()) {
- // Return all the removed children to the view pool
- for (TaskView tv : childrenToRemove) {
- mViewPool.returnViewToPool(tv);
- }
-
- if (!unifyNewViewAnimation) {
- // For views that are not already visible, animate them in
- childViewTransforms.clear();
- int duration = getEnterTransformsForFilterAnimation(tasks,
- taskTransforms, childViewTransforms);
- for (final TaskView tv : childViewTransforms.keySet()) {
- TaskViewTransform t = childViewTransforms.get(tv);
- tv.updateViewPropertiesToTaskTransform(t, duration);
- }
- }
- }
- }
- });
- tv.updateViewPropertiesToTaskTransform(t, duration);
- }
- }
-
@Override
public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
Task filteredTask) {
@@ -1124,7 +887,7 @@
// Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
updateMinMaxScroll(false);
- float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
+ float overlapHeight = mStackAlgorithm.getTaskOverlapHeight();
setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
boundScrollRaw();
@@ -1134,7 +897,10 @@
getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
// Animate
- doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
+ mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
+
+ // Notify any callbacks
+ mCb.onTaskStackFilterTriggered();
}
@Override
@@ -1154,10 +920,13 @@
getStackTransforms(tasks, getStackScroll(), null, true);
// Animate
- doFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
+ mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
// Clear the saved vars
mStashedScroll = 0;
+
+ // Notify any callbacks
+ mCb.onTaskStackUnfilterTriggered();
}
/**** ViewPoolConsumer Implementation ****/
@@ -1173,47 +942,34 @@
@Override
public void prepareViewToEnterPool(TaskView tv) {
Task task = tv.getTask();
- tv.resetViewProperties();
if (Console.Enabled) {
Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|returnToPool]",
tv.getTask() + " tv: " + tv);
}
// Report that this tasks's data is no longer being used
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- loader.unloadTaskData(task);
+ RecentsTaskLoader.getInstance().unloadTaskData(task);
// Detach the view from the hierarchy
detachViewFromParent(tv);
- // Disable hw layers on this view
+ // Disable HW layers
tv.disableHwLayers();
+
+ // Reset the view properties
+ tv.resetViewProperties();
}
@Override
- public void prepareViewToLeavePool(TaskView tv, Task prepareData, boolean isNewView) {
+ public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
if (Console.Enabled) {
Console.log(Constants.Log.ViewPool.PoolCallbacks, "[TaskStackView|leavePool]",
"isNewView: " + isNewView);
}
- // Setup and attach the view to the window
- Task task = prepareData;
- // We try and rebind the task (this MUST be done before the task filled)
+ // Rebind the task and request that this task's data be filled into the TaskView
tv.onTaskBound(task);
- // Request that this tasks's data be filled
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- loader.loadTaskData(task);
- // Find the index where this task should be placed in the children
- int insertIndex = -1;
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- Task tvTask = ((TaskView) getChildAt(i)).getTask();
- if (mStack.containsTask(task) && (mStack.indexOfTask(task) < mStack.indexOfTask(tvTask))) {
- insertIndex = i;
- break;
- }
- }
+ RecentsTaskLoader.getInstance().loadTaskData(task);
// Sanity check, the task view should always be clipping against the stack at this point,
// but just in case, re-enable it here
@@ -1224,6 +980,20 @@
tv.setNoUserInteractionState();
}
+ // Find the index where this task should be placed in the stack
+ int insertIndex = -1;
+ int taskIndex = mStack.indexOfTask(task);
+ if (taskIndex != -1) {
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ Task tvTask = ((TaskView) getChildAt(i)).getTask();
+ if (taskIndex < mStack.indexOfTask(tvTask)) {
+ insertIndex = i;
+ break;
+ }
+ }
+ }
+
// Add/attach the view to the hierarchy
if (Console.Enabled) {
Console.log(Constants.Log.ViewPool.PoolCallbacks, " [TaskStackView|insertIndex]",
@@ -1233,7 +1003,7 @@
addView(tv, insertIndex);
// Set the callbacks and listeners for this new view
- tv.setOnClickListener(this);
+ setTouchOnTaskView(tv, true);
tv.setCallbacks(this);
} else {
attachViewToParent(tv, insertIndex, tv.getLayoutParams());
@@ -1331,393 +1101,4 @@
}
}
}
-}
-
-/* Handles touch events */
-class TaskStackViewTouchHandler implements SwipeHelper.Callback {
- static int INACTIVE_POINTER_ID = -1;
-
- TaskStackView mSv;
- VelocityTracker mVelocityTracker;
-
- boolean mIsScrolling;
-
- int mInitialMotionX, mInitialMotionY;
- int mLastMotionX, mLastMotionY;
- int mActivePointerId = INACTIVE_POINTER_ID;
- TaskView mActiveTaskView = null;
-
- int mTotalScrollMotion;
- int mMinimumVelocity;
- int mMaximumVelocity;
- // The scroll touch slop is used to calculate when we start scrolling
- int mScrollTouchSlop;
- // The page touch slop is used to calculate when we start swiping
- float mPagingTouchSlop;
-
- SwipeHelper mSwipeHelper;
- boolean mInterceptedBySwipeHelper;
-
- public TaskStackViewTouchHandler(Context context, TaskStackView sv) {
- ViewConfiguration configuration = ViewConfiguration.get(context);
- mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
- mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
- mScrollTouchSlop = configuration.getScaledTouchSlop();
- mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
- mSv = sv;
-
-
- float densityScale = context.getResources().getDisplayMetrics().density;
- mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop);
- mSwipeHelper.setMinAlpha(1f);
- }
-
- /** Velocity tracker helpers */
- void initOrResetVelocityTracker() {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- } else {
- mVelocityTracker.clear();
- }
- }
- void initVelocityTrackerIfNotExists() {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- }
- void recycleVelocityTracker() {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- }
-
- /** Returns the view at the specified coordinates */
- TaskView findViewAtPoint(int x, int y) {
- int childCount = mSv.getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- TaskView tv = (TaskView) mSv.getChildAt(i);
- if (tv.getVisibility() == View.VISIBLE) {
- if (mSv.isTransformedTouchPointInView(x, y, tv)) {
- return tv;
- }
- }
- }
- return null;
- }
-
- /** Touch preprocessing for handling below */
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.TouchEvents,
- "[TaskStackViewTouchHandler|interceptTouchEvent]",
- Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
- }
-
- // Return early if we have no children
- boolean hasChildren = (mSv.getChildCount() > 0);
- if (!hasChildren) {
- return false;
- }
-
- // Pass through to swipe helper if we are swiping
- mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
- if (mInterceptedBySwipeHelper) {
- return true;
- }
-
- boolean wasScrolling = !mSv.mScroller.isFinished() ||
- (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning());
- int action = ev.getAction();
- switch (action & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN: {
- // Save the touch down info
- mInitialMotionX = mLastMotionX = (int) ev.getX();
- mInitialMotionY = mLastMotionY = (int) ev.getY();
- mActivePointerId = ev.getPointerId(0);
- mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
- // Stop the current scroll if it is still flinging
- mSv.abortScroller();
- mSv.abortBoundScrollAnimation();
- // Initialize the velocity tracker
- initOrResetVelocityTracker();
- mVelocityTracker.addMovement(ev);
- // Check if the scroller is finished yet
- mIsScrolling = !mSv.mScroller.isFinished();
- break;
- }
- case MotionEvent.ACTION_MOVE: {
- if (mActivePointerId == INACTIVE_POINTER_ID) break;
-
- int activePointerIndex = ev.findPointerIndex(mActivePointerId);
- int y = (int) ev.getY(activePointerIndex);
- int x = (int) ev.getX(activePointerIndex);
- if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
- // Save the touch move info
- mIsScrolling = true;
- // Initialize the velocity tracker if necessary
- initVelocityTrackerIfNotExists();
- mVelocityTracker.addMovement(ev);
- // Disallow parents from intercepting touch events
- final ViewParent parent = mSv.getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(true);
- }
- // Enable HW layers
- mSv.addHwLayersRefCount("stackScroll");
- }
-
- mLastMotionX = x;
- mLastMotionY = y;
- break;
- }
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP: {
- // Animate the scroll back if we've cancelled
- mSv.animateBoundScroll();
- // Disable HW layers
- if (mIsScrolling) {
- mSv.decHwLayersRefCount("stackScroll");
- }
- // Reset the drag state and the velocity tracker
- mIsScrolling = false;
- mActivePointerId = INACTIVE_POINTER_ID;
- mActiveTaskView = null;
- mTotalScrollMotion = 0;
- recycleVelocityTracker();
- break;
- }
- }
-
- return wasScrolling || mIsScrolling;
- }
-
- /** Handles touch events once we have intercepted them */
- public boolean onTouchEvent(MotionEvent ev) {
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.TouchEvents,
- "[TaskStackViewTouchHandler|touchEvent]",
- Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
- }
-
- // Short circuit if we have no children
- boolean hasChildren = (mSv.getChildCount() > 0);
- if (!hasChildren) {
- return false;
- }
-
- // Pass through to swipe helper if we are swiping
- if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
- return true;
- }
-
- // Update the velocity tracker
- initVelocityTrackerIfNotExists();
- mVelocityTracker.addMovement(ev);
-
- int action = ev.getAction();
- switch (action & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN: {
- // Save the touch down info
- mInitialMotionX = mLastMotionX = (int) ev.getX();
- mInitialMotionY = mLastMotionY = (int) ev.getY();
- mActivePointerId = ev.getPointerId(0);
- mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
- // Stop the current scroll if it is still flinging
- mSv.abortScroller();
- mSv.abortBoundScrollAnimation();
- // Initialize the velocity tracker
- initOrResetVelocityTracker();
- mVelocityTracker.addMovement(ev);
- // Disallow parents from intercepting touch events
- final ViewParent parent = mSv.getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(true);
- }
- break;
- }
- case MotionEvent.ACTION_POINTER_DOWN: {
- final int index = ev.getActionIndex();
- mActivePointerId = ev.getPointerId(index);
- mLastMotionX = (int) ev.getX(index);
- mLastMotionY = (int) ev.getY(index);
- break;
- }
- case MotionEvent.ACTION_MOVE: {
- if (mActivePointerId == INACTIVE_POINTER_ID) break;
-
- int activePointerIndex = ev.findPointerIndex(mActivePointerId);
- int x = (int) ev.getX(activePointerIndex);
- int y = (int) ev.getY(activePointerIndex);
- int yTotal = Math.abs(y - mInitialMotionY);
- int deltaY = mLastMotionY - y;
- if (!mIsScrolling) {
- if (yTotal > mScrollTouchSlop) {
- mIsScrolling = true;
- // Initialize the velocity tracker
- initOrResetVelocityTracker();
- mVelocityTracker.addMovement(ev);
- // Disallow parents from intercepting touch events
- final ViewParent parent = mSv.getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(true);
- }
- // Enable HW layers
- mSv.addHwLayersRefCount("stackScroll");
- }
- }
- if (mIsScrolling) {
- int curStackScroll = mSv.getStackScroll();
- int overScrollAmount = mSv.getScrollAmountOutOfBounds(curStackScroll + deltaY);
- if (overScrollAmount != 0) {
- // Bound the overscroll to a fixed amount, and inversely scale the y-movement
- // relative to how close we are to the max overscroll
- float maxOverScroll = mSv.mTaskRect.height() / 3f;
- deltaY = Math.round(deltaY * (1f - (Math.min(maxOverScroll, overScrollAmount)
- / maxOverScroll)));
- }
- mSv.setStackScroll(curStackScroll + deltaY);
- if (mSv.isScrollOutOfBounds()) {
- mVelocityTracker.clear();
- }
- }
- mLastMotionX = x;
- mLastMotionY = y;
- mTotalScrollMotion += Math.abs(deltaY);
- break;
- }
- case MotionEvent.ACTION_UP: {
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int velocity = (int) velocityTracker.getYVelocity(mActivePointerId);
-
- if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
- // Enable HW layers on the stack
- mSv.addHwLayersRefCount("flingScroll");
- // XXX: Make this animation a function of the velocity AND distance
- int overscrollRange = (int) (Math.min(1f,
- Math.abs((float) velocity / mMaximumVelocity)) *
- Constants.Values.TaskStackView.TaskStackOverscrollRange);
-
- if (Console.Enabled) {
- Console.log(Constants.Log.UI.TouchEvents,
- "[TaskStackViewTouchHandler|fling]",
- "scroll: " + mSv.getStackScroll() + " velocity: " + velocity +
- " maxVelocity: " + mMaximumVelocity +
- " overscrollRange: " + overscrollRange,
- Console.AnsiGreen);
- }
-
- // Fling scroll
- mSv.mScroller.fling(0, mSv.getStackScroll(),
- 0, -velocity,
- 0, 0,
- mSv.mMinScroll, mSv.mMaxScroll,
- 0, overscrollRange);
- // Invalidate to kick off computeScroll
- mSv.invalidate(mSv.mStackRect);
- } else if (mSv.isScrollOutOfBounds()) {
- // Animate the scroll back into bounds
- // XXX: Make this animation a function of the velocity OR distance
- mSv.animateBoundScroll();
- }
-
- if (mIsScrolling) {
- // Disable HW layers
- mSv.decHwLayersRefCount("stackScroll");
- }
- mActivePointerId = INACTIVE_POINTER_ID;
- mIsScrolling = false;
- mTotalScrollMotion = 0;
- recycleVelocityTracker();
- break;
- }
- case MotionEvent.ACTION_POINTER_UP: {
- int pointerIndex = ev.getActionIndex();
- int pointerId = ev.getPointerId(pointerIndex);
- if (pointerId == mActivePointerId) {
- // Select a new active pointer id and reset the motion state
- final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
- mActivePointerId = ev.getPointerId(newPointerIndex);
- mLastMotionX = (int) ev.getX(newPointerIndex);
- mLastMotionY = (int) ev.getY(newPointerIndex);
- mVelocityTracker.clear();
- }
- break;
- }
- case MotionEvent.ACTION_CANCEL: {
- if (mIsScrolling) {
- // Disable HW layers
- mSv.decHwLayersRefCount("stackScroll");
- }
- if (mSv.isScrollOutOfBounds()) {
- // Animate the scroll back into bounds
- // XXX: Make this animation a function of the velocity OR distance
- mSv.animateBoundScroll();
- }
- mActivePointerId = INACTIVE_POINTER_ID;
- mIsScrolling = false;
- mTotalScrollMotion = 0;
- recycleVelocityTracker();
- break;
- }
- }
- return true;
- }
-
- /**** SwipeHelper Implementation ****/
-
- @Override
- public View getChildAtPosition(MotionEvent ev) {
- return findViewAtPoint((int) ev.getX(), (int) ev.getY());
- }
-
- @Override
- public boolean canChildBeDismissed(View v) {
- return true;
- }
-
- @Override
- public void onBeginDrag(View v) {
- // Enable HW layers
- mSv.addHwLayersRefCount("swipeBegin");
- // Disable clipping with the stack while we are swiping
- TaskView tv = (TaskView) v;
- tv.setClipViewInStack(false);
- // Disallow parents from intercepting touch events
- final ViewParent parent = mSv.getParent();
- if (parent != null) {
- parent.requestDisallowInterceptTouchEvent(true);
- }
- }
-
- @Override
- public void onSwipeChanged(View v, float delta) {
- // Do nothing
- }
-
- @Override
- public void onChildDismissed(View v) {
- TaskView tv = (TaskView) v;
- mSv.onTaskDismissed(tv);
-
- // Re-enable clipping with the stack (we will reuse this view)
- tv.setClipViewInStack(true);
-
- // Disable HW layers
- mSv.decHwLayersRefCount("swipeComplete");
- }
-
- @Override
- public void onSnapBackCompleted(View v) {
- // Re-enable clipping with the stack
- TaskView tv = (TaskView) v;
- tv.setClipViewInStack(true);
- }
-
- @Override
- public void onDragCancelled(View v) {
- // Disable HW layers
- mSv.decHwLayersRefCount("swipeCancelled");
- }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
new file mode 100644
index 0000000..9cd5ae4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
@@ -0,0 +1,176 @@
+/*
+ * 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.systemui.recents.views;
+
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.model.Task;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/* The layout logic for a TaskStackView */
+public class TaskStackViewFilterAlgorithm {
+
+ RecentsConfiguration mConfig;
+ TaskStackView mStackView;
+ ViewPool<TaskView, Task> mViewPool;
+
+ public TaskStackViewFilterAlgorithm(RecentsConfiguration config, TaskStackView stackView,
+ ViewPool<TaskView, Task> viewPool) {
+ mConfig = config;
+ mStackView = stackView;
+ mViewPool = viewPool;
+ }
+
+ /** Orchestrates the animations of the current child views and any new views. */
+ void startFilteringAnimation(ArrayList<Task> curTasks,
+ ArrayList<TaskViewTransform> curTaskTransforms,
+ final ArrayList<Task> tasks,
+ final ArrayList<TaskViewTransform> taskTransforms) {
+ // Calculate the transforms to animate out all the existing views if they are not in the
+ // new visible range (or to their final positions in the stack if they are)
+ final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>();
+ final HashMap<TaskView, TaskViewTransform> childViewTransforms =
+ new HashMap<TaskView, TaskViewTransform>();
+ int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks,
+ taskTransforms, childViewTransforms, childrenToRemove);
+
+ // If all the current views are in the visible range of the new stack, then don't wait for
+ // views to animate out and animate all the new views into their place
+ final boolean unifyNewViewAnimation = childrenToRemove.isEmpty();
+ if (unifyNewViewAnimation) {
+ int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms,
+ childViewTransforms);
+ duration = Math.max(duration, inDuration);
+ }
+
+ // Animate all the views to their final transforms
+ for (final TaskView tv : childViewTransforms.keySet()) {
+ TaskViewTransform t = childViewTransforms.get(tv);
+ tv.animate().cancel();
+ tv.animate()
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ childViewTransforms.remove(tv);
+ if (childViewTransforms.isEmpty()) {
+ // Return all the removed children to the view pool
+ for (TaskView tv : childrenToRemove) {
+ mViewPool.returnViewToPool(tv);
+ }
+
+ if (!unifyNewViewAnimation) {
+ // For views that are not already visible, animate them in
+ childViewTransforms.clear();
+ int duration = getEnterTransformsForFilterAnimation(tasks,
+ taskTransforms, childViewTransforms);
+ for (final TaskView tv : childViewTransforms.keySet()) {
+ TaskViewTransform t = childViewTransforms.get(tv);
+ tv.updateViewPropertiesToTaskTransform(t, duration);
+ }
+ }
+ }
+ }
+ });
+ tv.updateViewPropertiesToTaskTransform(t, duration);
+ }
+ }
+
+ /**
+ * Creates the animations for all the children views that need to be animated in when we are
+ * un/filtering a stack, and returns the duration for these animations.
+ */
+ int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks,
+ ArrayList<TaskViewTransform> taskTransforms,
+ HashMap<TaskView, TaskViewTransform> childViewTransformsOut) {
+ int offset = 0;
+ int movement = 0;
+ int taskCount = tasks.size();
+ for (int i = taskCount - 1; i >= 0; i--) {
+ Task task = tasks.get(i);
+ TaskViewTransform toTransform = taskTransforms.get(i);
+ if (toTransform.visible) {
+ TaskView tv = mStackView.getChildViewForTask(task);
+ if (tv == null) {
+ // For views that are not already visible, animate them in
+ tv = mViewPool.pickUpViewFromPool(task, task);
+
+ // Compose a new transform to fade and slide the new task in
+ TaskViewTransform fromTransform = new TaskViewTransform(toTransform);
+ tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
+ tv.updateViewPropertiesToTaskTransform(fromTransform, 0);
+
+ toTransform.startDelay = offset * Constants.Values.TaskStackView.FilterStartDelay;
+ childViewTransformsOut.put(tv, toTransform);
+
+ // Use the movement of the new views to calculate the duration of the animation
+ movement = Math.max(movement,
+ Math.abs(toTransform.translationY - fromTransform.translationY));
+ offset++;
+ }
+ }
+ }
+ return mConfig.filteringNewViewsAnimDuration;
+ }
+
+ /**
+ * Creates the animations for all the children views that need to be removed or to move views
+ * to their un/filtered position when we are un/filtering a stack, and returns the duration
+ * for these animations.
+ */
+ int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks,
+ ArrayList<TaskViewTransform> curTaskTransforms,
+ ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms,
+ HashMap<TaskView, TaskViewTransform> childViewTransformsOut,
+ ArrayList<TaskView> childrenToRemoveOut) {
+ // Animate all of the existing views out of view (if they are not in the visible range in
+ // the new stack) or to their final positions in the new stack
+ int offset = 0;
+ int movement = 0;
+ int childCount = mStackView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ TaskView tv = (TaskView) mStackView.getChildAt(i);
+ Task task = tv.getTask();
+ int taskIndex = tasks.indexOf(task);
+ TaskViewTransform toTransform;
+
+ // If the view is no longer visible, then we should just animate it out
+ boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible;
+ if (willBeInvisible) {
+ if (taskIndex < 0) {
+ toTransform = curTaskTransforms.get(curTasks.indexOf(task));
+ } else {
+ toTransform = new TaskViewTransform(taskTransforms.get(taskIndex));
+ }
+ tv.prepareTaskTransformForFilterTaskVisible(toTransform);
+ childrenToRemoveOut.add(tv);
+ } else {
+ toTransform = taskTransforms.get(taskIndex);
+ // Use the movement of the visible views to calculate the duration of the animation
+ movement = Math.max(movement, Math.abs(toTransform.translationY -
+ (int) tv.getTranslationY()));
+ }
+
+ toTransform.startDelay = offset * Constants.Values.TaskStackView.FilterStartDelay;
+ childViewTransformsOut.put(tv, toTransform);
+ offset++;
+ }
+ return mConfig.filteringCurrentViewsAnimDuration;
+ }
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
new file mode 100644
index 0000000..daa18bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -0,0 +1,173 @@
+/*
+ * 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.systemui.recents.views;
+
+import android.graphics.Rect;
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.Utilities;
+
+/* The layout logic for a TaskStackView */
+public class TaskStackViewLayoutAlgorithm {
+
+ // These are all going to change
+ static final float StackOverlapPct = 0.65f; // The overlap height relative to the task height
+ static final float StackPeekHeightPct = 0.1f; // The height of the peek space relative to the stack height
+ static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area
+ static final int StackPeekNumCards = 3; // The number of cards we see in the peek space
+
+ RecentsConfiguration mConfig;
+
+ // The various rects that define the stack view
+ Rect mRect = new Rect();
+ Rect mStackRect = new Rect();
+ Rect mStackRectSansPeek = new Rect();
+ Rect mTaskRect = new Rect();
+
+ // The min/max scroll
+ int mMinScroll;
+ int mMaxScroll;
+
+ public TaskStackViewLayoutAlgorithm(RecentsConfiguration config) {
+ mConfig = config;
+ }
+
+ /** Computes the stack and task rects */
+ public void computeRects(int width, int height, int insetLeft, int insetBottom) {
+ // Note: We let the stack view be the full height because we want the cards to go under the
+ // navigation bar if possible. However, the stack rects which we use to calculate
+ // max scroll, etc. need to take the nav bar into account
+
+ // Compute the stack rects
+ mRect.set(0, 0, width, height);
+ mStackRect.set(mRect);
+ mStackRect.left += insetLeft;
+ mStackRect.bottom -= insetBottom;
+
+ int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
+ int heightPadding = mConfig.taskStackTopPaddingPx;
+ if (Constants.DebugFlags.App.EnableSearchLayout) {
+ mStackRect.top += heightPadding;
+ mStackRect.left += widthPadding;
+ mStackRect.right -= widthPadding;
+ mStackRect.bottom -= heightPadding;
+ } else {
+ mStackRect.inset(widthPadding, heightPadding);
+ }
+ mStackRectSansPeek.set(mStackRect);
+ mStackRectSansPeek.top += StackPeekHeightPct * mStackRect.height();
+
+ // Compute the task rect
+ int size = mStackRect.width();
+ int left = mStackRect.left + (mStackRect.width() - size) / 2;
+ mTaskRect.set(left, mStackRectSansPeek.top,
+ left + size, mStackRectSansPeek.top + size);
+ }
+
+ void computeMinMaxScroll(int taskCount) {
+ // Compute the min and max scroll values
+ int numTasks = Math.max(1, taskCount);
+ int taskHeight = mTaskRect.height();
+ int stackHeight = mStackRectSansPeek.height();
+ int maxScrollHeight = taskHeight + getStackScrollForTaskIndex(numTasks - 1);
+
+ if (numTasks <= 1) {
+ // If there is only one task, then center the task in the stack rect (sans peek)
+ mMinScroll = mMaxScroll = -(stackHeight - taskHeight) / 2;
+ } else {
+ mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
+ mMaxScroll = maxScrollHeight - stackHeight;
+ }
+ }
+
+ /** Update/get the transform (creates a new TaskViewTransform) */
+ public TaskViewTransform getStackTransform(int indexInStack, int stackScroll) {
+ TaskViewTransform transform = new TaskViewTransform();
+ return getStackTransform(indexInStack, stackScroll, transform);
+ }
+
+ /** Update/get the transform */
+ public TaskViewTransform getStackTransform(int indexInStack, int stackScroll,
+ TaskViewTransform transformOut) {
+ // Return early if we have an invalid index
+ if (indexInStack < 0) {
+ transformOut.reset();
+ return transformOut;
+ }
+
+ // Map the items to an continuous position relative to the specified scroll
+ int numPeekCards = StackPeekNumCards;
+ float overlapHeight = StackOverlapPct * mTaskRect.height();
+ float peekHeight = StackPeekHeightPct * mStackRect.height();
+ float t = ((indexInStack * overlapHeight) - stackScroll) / overlapHeight;
+ float boundedT = Math.max(t, -(numPeekCards + 1));
+
+ // Set the scale relative to its position
+ int numFrontScaledCards = 3;
+ float minScale = StackPeekMinScale;
+ float scaleRange = 1f - minScale;
+ float scaleInc = scaleRange / (numPeekCards + numFrontScaledCards);
+ float scale = Math.max(minScale, Math.min(1f, minScale +
+ ((boundedT + (numPeekCards + 1)) * scaleInc)));
+ float scaleYOffset = ((1f - scale) * mTaskRect.height()) / 2;
+ transformOut.scale = scale;
+
+ // Set the y translation
+ if (boundedT < 0f) {
+ transformOut.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
+ numPeekCards) * peekHeight - scaleYOffset);
+ } else {
+ transformOut.translationY = (int) (boundedT * overlapHeight - scaleYOffset);
+ }
+
+ // Set the z translation
+ int minZ = mConfig.taskViewTranslationZMinPx;
+ int incZ = mConfig.taskViewTranslationZIncrementPx;
+ transformOut.translationZ = (int) Math.max(minZ, minZ + ((boundedT + numPeekCards) * incZ));
+
+ // Set the alphas
+ transformOut.dismissAlpha = Math.max(-1f, Math.min(0f, t + 1)) + 1f;
+
+ // Update the rect and visibility
+ transformOut.rect.set(mTaskRect);
+ if (t < -(numPeekCards + 1)) {
+ transformOut.visible = false;
+ } else {
+ transformOut.rect.offset(0, transformOut.translationY);
+ Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+ transformOut.visible = Rect.intersects(mRect, transformOut.rect);
+ }
+ transformOut.t = t;
+ return transformOut;
+ }
+
+ /**
+ * Returns the overlap between one task and the next.
+ */
+ float getTaskOverlapHeight() {
+ return StackOverlapPct * mTaskRect.height();
+ }
+
+ /**
+ * Returns the scroll to such that the task transform at that index will have t=0. (If the scroll
+ * is not bounded)
+ */
+ int getStackScrollForTaskIndex(int i) {
+ return (int) (i * getTaskOverlapHeight());
+ }
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
new file mode 100644
index 0000000..51f994e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -0,0 +1,423 @@
+/*
+ * 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.systemui.recents.views;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+import com.android.systemui.recents.Console;
+import com.android.systemui.recents.Constants;
+
+/* Handles touch events for a TaskStackView. */
+class TaskStackViewTouchHandler implements SwipeHelper.Callback {
+ static int INACTIVE_POINTER_ID = -1;
+
+ TaskStackView mSv;
+ VelocityTracker mVelocityTracker;
+
+ boolean mIsScrolling;
+
+ int mInitialMotionX, mInitialMotionY;
+ int mLastMotionX, mLastMotionY;
+ int mActivePointerId = INACTIVE_POINTER_ID;
+ TaskView mActiveTaskView = null;
+
+ int mTotalScrollMotion;
+ int mMinimumVelocity;
+ int mMaximumVelocity;
+ // The scroll touch slop is used to calculate when we start scrolling
+ int mScrollTouchSlop;
+ // The page touch slop is used to calculate when we start swiping
+ float mPagingTouchSlop;
+
+ SwipeHelper mSwipeHelper;
+ boolean mInterceptedBySwipeHelper;
+
+ public TaskStackViewTouchHandler(Context context, TaskStackView sv) {
+ ViewConfiguration configuration = ViewConfiguration.get(context);
+ mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mScrollTouchSlop = configuration.getScaledTouchSlop();
+ mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
+ mSv = sv;
+
+
+ float densityScale = context.getResources().getDisplayMetrics().density;
+ mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, mPagingTouchSlop);
+ mSwipeHelper.setMinAlpha(1f);
+ }
+
+ /** Velocity tracker helpers */
+ void initOrResetVelocityTracker() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ } else {
+ mVelocityTracker.clear();
+ }
+ }
+ void initVelocityTrackerIfNotExists() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ }
+ void recycleVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ /** Returns the view at the specified coordinates */
+ TaskView findViewAtPoint(int x, int y) {
+ int childCount = mSv.getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ TaskView tv = (TaskView) mSv.getChildAt(i);
+ if (tv.getVisibility() == View.VISIBLE) {
+ if (mSv.isTransformedTouchPointInView(x, y, tv)) {
+ return tv;
+ }
+ }
+ }
+ return null;
+ }
+
+ /** Touch preprocessing for handling below */
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (Console.Enabled) {
+ Console.log(Constants.Log.UI.TouchEvents,
+ "[TaskStackViewTouchHandler|interceptTouchEvent]",
+ Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
+ }
+
+ // Return early if we have no children
+ boolean hasChildren = (mSv.getChildCount() > 0);
+ if (!hasChildren) {
+ return false;
+ }
+
+ // Pass through to swipe helper if we are swiping
+ mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev);
+ if (mInterceptedBySwipeHelper) {
+ return true;
+ }
+
+ boolean wasScrolling = !mSv.mScroller.isFinished() ||
+ (mSv.mScrollAnimator != null && mSv.mScrollAnimator.isRunning());
+ int action = ev.getAction();
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: {
+ // Save the touch down info
+ mInitialMotionX = mLastMotionX = (int) ev.getX();
+ mInitialMotionY = mLastMotionY = (int) ev.getY();
+ mActivePointerId = ev.getPointerId(0);
+ mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
+ // Stop the current scroll if it is still flinging
+ mSv.abortScroller();
+ mSv.abortBoundScrollAnimation();
+ // Initialize the velocity tracker
+ initOrResetVelocityTracker();
+ mVelocityTracker.addMovement(ev);
+ // Check if the scroller is finished yet
+ mIsScrolling = !mSv.mScroller.isFinished();
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ if (mActivePointerId == INACTIVE_POINTER_ID) break;
+
+ int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ int y = (int) ev.getY(activePointerIndex);
+ int x = (int) ev.getX(activePointerIndex);
+ if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
+ // Save the touch move info
+ mIsScrolling = true;
+ // Initialize the velocity tracker if necessary
+ initVelocityTrackerIfNotExists();
+ mVelocityTracker.addMovement(ev);
+ // Disallow parents from intercepting touch events
+ final ViewParent parent = mSv.getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ // Enable HW layers
+ mSv.addHwLayersRefCount("stackScroll");
+ }
+
+ mLastMotionX = x;
+ mLastMotionY = y;
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP: {
+ // Animate the scroll back if we've cancelled
+ mSv.animateBoundScroll();
+ // Disable HW layers
+ if (mIsScrolling) {
+ mSv.decHwLayersRefCount("stackScroll");
+ }
+ // Reset the drag state and the velocity tracker
+ mIsScrolling = false;
+ mActivePointerId = INACTIVE_POINTER_ID;
+ mActiveTaskView = null;
+ mTotalScrollMotion = 0;
+ recycleVelocityTracker();
+ break;
+ }
+ }
+
+ return wasScrolling || mIsScrolling;
+ }
+
+ /** Handles touch events once we have intercepted them */
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (Console.Enabled) {
+ Console.log(Constants.Log.UI.TouchEvents,
+ "[TaskStackViewTouchHandler|touchEvent]",
+ Console.motionEventActionToString(ev.getAction()), Console.AnsiBlue);
+ }
+
+ // Short circuit if we have no children
+ boolean hasChildren = (mSv.getChildCount() > 0);
+ if (!hasChildren) {
+ return false;
+ }
+
+ // Pass through to swipe helper if we are swiping
+ if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) {
+ return true;
+ }
+
+ // Update the velocity tracker
+ initVelocityTrackerIfNotExists();
+ mVelocityTracker.addMovement(ev);
+
+ int action = ev.getAction();
+ switch (action & MotionEvent.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: {
+ // Save the touch down info
+ mInitialMotionX = mLastMotionX = (int) ev.getX();
+ mInitialMotionY = mLastMotionY = (int) ev.getY();
+ mActivePointerId = ev.getPointerId(0);
+ mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY);
+ // Stop the current scroll if it is still flinging
+ mSv.abortScroller();
+ mSv.abortBoundScrollAnimation();
+ // Initialize the velocity tracker
+ initOrResetVelocityTracker();
+ mVelocityTracker.addMovement(ev);
+ // Disallow parents from intercepting touch events
+ final ViewParent parent = mSv.getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ final int index = ev.getActionIndex();
+ mActivePointerId = ev.getPointerId(index);
+ mLastMotionX = (int) ev.getX(index);
+ mLastMotionY = (int) ev.getY(index);
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ if (mActivePointerId == INACTIVE_POINTER_ID) break;
+
+ int activePointerIndex = ev.findPointerIndex(mActivePointerId);
+ int x = (int) ev.getX(activePointerIndex);
+ int y = (int) ev.getY(activePointerIndex);
+ int yTotal = Math.abs(y - mInitialMotionY);
+ int deltaY = mLastMotionY - y;
+ if (!mIsScrolling) {
+ if (yTotal > mScrollTouchSlop) {
+ mIsScrolling = true;
+ // Initialize the velocity tracker
+ initOrResetVelocityTracker();
+ mVelocityTracker.addMovement(ev);
+ // Disallow parents from intercepting touch events
+ final ViewParent parent = mSv.getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ // Enable HW layers
+ mSv.addHwLayersRefCount("stackScroll");
+ }
+ }
+ if (mIsScrolling) {
+ int curStackScroll = mSv.getStackScroll();
+ int overScrollAmount = mSv.getScrollAmountOutOfBounds(curStackScroll + deltaY);
+ if (overScrollAmount != 0) {
+ // Bound the overscroll to a fixed amount, and inversely scale the y-movement
+ // relative to how close we are to the max overscroll
+ float maxOverScroll = mSv.mStackAlgorithm.mTaskRect.height() / 3f;
+ deltaY = Math.round(deltaY * (1f - (Math.min(maxOverScroll, overScrollAmount)
+ / maxOverScroll)));
+ }
+ mSv.setStackScroll(curStackScroll + deltaY);
+ if (mSv.isScrollOutOfBounds()) {
+ mVelocityTracker.clear();
+ }
+ }
+ mLastMotionX = x;
+ mLastMotionY = y;
+ mTotalScrollMotion += Math.abs(deltaY);
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int velocity = (int) velocityTracker.getYVelocity(mActivePointerId);
+
+ if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) {
+ // Enable HW layers on the stack
+ mSv.addHwLayersRefCount("flingScroll");
+ // XXX: Make this animation a function of the velocity AND distance
+ int overscrollRange = (int) (Math.min(1f,
+ Math.abs((float) velocity / mMaximumVelocity)) *
+ Constants.Values.TaskStackView.TaskStackOverscrollRange);
+
+ if (Console.Enabled) {
+ Console.log(Constants.Log.UI.TouchEvents,
+ "[TaskStackViewTouchHandler|fling]",
+ "scroll: " + mSv.getStackScroll() + " velocity: " + velocity +
+ " maxVelocity: " + mMaximumVelocity +
+ " overscrollRange: " + overscrollRange,
+ Console.AnsiGreen);
+ }
+
+ // Fling scroll
+ mSv.mScroller.fling(0, mSv.getStackScroll(),
+ 0, -velocity,
+ 0, 0,
+ mSv.mMinScroll, mSv.mMaxScroll,
+ 0, overscrollRange);
+ // Invalidate to kick off computeScroll
+ mSv.invalidate(mSv.mStackAlgorithm.mStackRect);
+ } else if (mSv.isScrollOutOfBounds()) {
+ // Animate the scroll back into bounds
+ // XXX: Make this animation a function of the velocity OR distance
+ mSv.animateBoundScroll();
+ }
+
+ if (mIsScrolling) {
+ // Disable HW layers
+ mSv.decHwLayersRefCount("stackScroll");
+ }
+ mActivePointerId = INACTIVE_POINTER_ID;
+ mIsScrolling = false;
+ mTotalScrollMotion = 0;
+ recycleVelocityTracker();
+ break;
+ }
+ case MotionEvent.ACTION_POINTER_UP: {
+ int pointerIndex = ev.getActionIndex();
+ int pointerId = ev.getPointerId(pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // Select a new active pointer id and reset the motion state
+ final int newPointerIndex = (pointerIndex == 0) ? 1 : 0;
+ mActivePointerId = ev.getPointerId(newPointerIndex);
+ mLastMotionX = (int) ev.getX(newPointerIndex);
+ mLastMotionY = (int) ev.getY(newPointerIndex);
+ mVelocityTracker.clear();
+ }
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL: {
+ if (mIsScrolling) {
+ // Disable HW layers
+ mSv.decHwLayersRefCount("stackScroll");
+ }
+ if (mSv.isScrollOutOfBounds()) {
+ // Animate the scroll back into bounds
+ // XXX: Make this animation a function of the velocity OR distance
+ mSv.animateBoundScroll();
+ }
+ mActivePointerId = INACTIVE_POINTER_ID;
+ mIsScrolling = false;
+ mTotalScrollMotion = 0;
+ recycleVelocityTracker();
+ break;
+ }
+ }
+ return true;
+ }
+
+ /**** SwipeHelper Implementation ****/
+
+ @Override
+ public View getChildAtPosition(MotionEvent ev) {
+ return findViewAtPoint((int) ev.getX(), (int) ev.getY());
+ }
+
+ @Override
+ public boolean canChildBeDismissed(View v) {
+ return true;
+ }
+
+ @Override
+ public void onBeginDrag(View v) {
+ TaskView tv = (TaskView) v;
+ // Disable clipping with the stack while we are swiping
+ tv.setClipViewInStack(false);
+ // Enable HW layers on that task
+ tv.enableHwLayers();
+ // Disallow touch events from this task view
+ mSv.setTouchOnTaskView(tv, false);
+ // Disallow parents from intercepting touch events
+ final ViewParent parent = mSv.getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ }
+
+ @Override
+ public void onSwipeChanged(View v, float delta) {
+ // Do nothing
+ }
+
+ @Override
+ public void onChildDismissed(View v) {
+ TaskView tv = (TaskView) v;
+ // Disable HW layers on that task
+ if (mSv.mHwLayersTrigger.getCount() == 0) {
+ tv.disableHwLayers();
+ }
+ // Re-enable clipping with the stack (we will reuse this view)
+ tv.setClipViewInStack(true);
+ // Remove the task view from the stack
+ mSv.onTaskDismissed(tv);
+ }
+
+ @Override
+ public void onSnapBackCompleted(View v) {
+ onDragCancelled(v);
+ }
+
+ @Override
+ public void onDragCancelled(View v) {
+ TaskView tv = (TaskView) v;
+ // Disable HW layers on that task
+ if (mSv.mHwLayersTrigger.getCount() == 0) {
+ tv.disableHwLayers();
+ }
+ // Re-enable clipping with the stack
+ tv.setClipViewInStack(true);
+ // Re-enable touch events from this task view
+ mSv.setTouchOnTaskView(tv, true);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
index c2b2094..636746d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskThumbnailView.java
@@ -83,7 +83,7 @@
}
/** Binds the thumbnail view to the task */
- void rebindToTask(Task t, boolean animate) {
+ void rebindToTask(Task t) {
mTask = t;
if (t.thumbnail != null) {
setImageBitmap(t.thumbnail);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index cfba74c..b1fc700 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -123,7 +123,7 @@
mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
if (mTaskDataLoaded) {
- onTaskDataLoaded(false);
+ onTaskDataLoaded();
}
}
@@ -485,7 +485,7 @@
/** Compute the dim as a function of the scale of this view. */
int getDimOverlayFromScale() {
- float minScale = Constants.Values.TaskStackView.StackPeekMinScale;
+ float minScale = TaskStackViewLayoutAlgorithm.StackPeekMinScale;
float scaleRange = 1f - minScale;
float dim = (1f - getScaleX()) / scaleRange;
dim = mDimInterpolator.getInterpolation(Math.min(dim, 1f));
@@ -551,11 +551,11 @@
}
@Override
- public void onTaskDataLoaded(boolean reloadingTaskData) {
+ public void onTaskDataLoaded() {
if (mThumbnailView != null && mBarView != null) {
// Bind each of the views to the new task data
- mThumbnailView.rebindToTask(mTask, reloadingTaskData);
- mBarView.rebindToTask(mTask, reloadingTaskData);
+ mThumbnailView.rebindToTask(mTask);
+ mBarView.rebindToTask(mTask);
// Rebind any listeners
mBarView.mApplicationIcon.setOnClickListener(this);
mBarView.mDismissButton.setOnClickListener(this);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
index b5e8ffd..13407aa 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
@@ -25,7 +25,7 @@
/* The animation context for a task view animation into Recents */
public static class TaskViewEnterContext {
// The full screenshot view that we are animating down
- FullScreenTransitionView fullScreenshot;
+ FullscreenTransitionOverlayView fullScreenshot;
// The transform of the current task view
TaskViewTransform transform;
// The stack rect that the transform is relative to
@@ -39,7 +39,7 @@
// Whether this is the front most task view
boolean isFrontMost;
- public TaskViewEnterContext(FullScreenTransitionView fss) {
+ public TaskViewEnterContext(FullscreenTransitionOverlayView fss) {
fullScreenshot = fss;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index f6f78e9..1550217 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -25,6 +25,8 @@
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@@ -89,6 +91,8 @@
= new PathInterpolator(0, 0, 0.5f, 1);
private boolean mDimmed;
+ private boolean mDark;
+ private final Paint mDarkPaint = createDarkPaint();
private int mBgResId = com.android.internal.R.drawable.notification_material_bg;
private int mDimmedBgResId = com.android.internal.R.drawable.notification_material_bg_dim;
@@ -295,6 +299,34 @@
}
}
+ public void setDark(boolean dark, boolean fade) {
+ // TODO implement fade
+ if (mDark != dark) {
+ mDark = dark;
+ if (mDark) {
+ setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+ } else {
+ setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+ }
+
+ private static Paint createDarkPaint() {
+ final Paint p = new Paint();
+ final float[] invert = {
+ -1f, 0f, 0f, 1f, 1f,
+ 0f, -1f, 0f, 1f, 1f,
+ 0f, 0f, -1f, 1f, 1f,
+ 0f, 0f, 0f, 1f, 0f
+ };
+ final ColorMatrix m = new ColorMatrix(invert);
+ final ColorMatrix grayscale = new ColorMatrix();
+ grayscale.setSaturation(0);
+ m.preConcat(grayscale);
+ p.setColorFilter(new ColorMatrixColorFilter(m));
+ return p;
+ }
+
/**
* Sets the resource id for the background of this notification.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 5bc23b5..c621f31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -94,7 +94,6 @@
public static final String TAG = "StatusBar";
public static final boolean DEBUG = false;
public static final boolean MULTIUSER_DEBUG = false;
- private static final boolean USE_NOTIFICATION_LISTENER = true;
protected static final int MSG_SHOW_RECENT_APPS = 1019;
protected static final int MSG_HIDE_RECENT_APPS = 1020;
@@ -298,7 +297,7 @@
@Override
public void run() {
for (StatusBarNotification sbn : notifications) {
- addNotificationInternal(sbn, currentRanking);
+ addNotification(sbn, currentRanking);
}
}
});
@@ -325,16 +324,16 @@
// wasn't a group child, remove the old instance.
// Otherwise just update the ranking.
if (isUpdate) {
- removeNotificationInternal(sbn.getKey(), rankingMap);
+ removeNotification(sbn.getKey(), rankingMap);
} else {
- updateRankingInternal(rankingMap);
+ updateNotificationRanking(rankingMap);
}
return;
}
if (isUpdate) {
- updateNotificationInternal(sbn, rankingMap);
+ updateNotification(sbn, rankingMap);
} else {
- addNotificationInternal(sbn, rankingMap);
+ addNotification(sbn, rankingMap);
}
}
});
@@ -347,7 +346,7 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- removeNotificationInternal(sbn.getKey(), rankingMap);
+ removeNotification(sbn.getKey(), rankingMap);
}
});
}
@@ -358,7 +357,7 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- updateRankingInternal(rankingMap);
+ updateNotificationRanking(rankingMap);
}
});
}
@@ -414,14 +413,12 @@
// Connect in to the status bar manager service
StatusBarIconList iconList = new StatusBarIconList();
- ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
mCommandQueue = new CommandQueue(this, iconList);
int[] switches = new int[8];
ArrayList<IBinder> binders = new ArrayList<IBinder>();
try {
- mBarService.registerStatusBar(mCommandQueue, iconList, notifications,
- switches, binders);
+ mBarService.registerStatusBar(mCommandQueue, iconList, switches, binders);
} catch (RemoteException ex) {
// If the system process isn't there we're doomed anyway.
}
@@ -447,19 +444,12 @@
}
// Set up the initial notification state.
- if (USE_NOTIFICATION_LISTENER) {
- try {
- mNotificationListener.registerAsSystemService(
- new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
- UserHandle.USER_ALL);
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to register notification listener", e);
- }
- } else {
- N = notifications.size();
- for (int i=0; i<N; i++) {
- addNotification(notifications.get(i));
- }
+ try {
+ mNotificationListener.registerAsSystemService(
+ new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
+ UserHandle.USER_ALL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to register notification listener", e);
}
@@ -1194,7 +1184,7 @@
* WARNING: this will call back into us. Don't hold any locks.
*/
void handleNotificationError(StatusBarNotification n, String message) {
- removeNotification(n.getKey());
+ removeNotification(n.getKey(), null);
try {
mBarService.onNotificationError(n.getPackageName(), n.getTag(), n.getId(), n.getUid(),
n.getInitialPid(), message, n.getUserId());
@@ -1330,34 +1320,11 @@
protected abstract void updateExpandedViewPos(int expandedPosition);
protected abstract boolean shouldDisableNavbarGestures();
- @Override
- public void addNotification(StatusBarNotification notification) {
- if (!USE_NOTIFICATION_LISTENER) {
- addNotificationInternal(notification, null);
- }
- }
-
- public abstract void addNotificationInternal(StatusBarNotification notification,
+ public abstract void addNotification(StatusBarNotification notification,
RankingMap ranking);
-
- protected abstract void updateRankingInternal(RankingMap ranking);
-
- @Override
- public void removeNotification(String key) {
- if (!USE_NOTIFICATION_LISTENER) {
- removeNotificationInternal(key, null);
- }
- }
-
- public abstract void removeNotificationInternal(String key, RankingMap ranking);
-
- public void updateNotification(StatusBarNotification notification) {
- if (!USE_NOTIFICATION_LISTENER) {
- updateNotificationInternal(notification, null);
- }
- }
-
- public void updateNotificationInternal(StatusBarNotification notification, RankingMap ranking) {
+ protected abstract void updateNotificationRanking(RankingMap ranking);
+ public abstract void removeNotification(String key, RankingMap ranking);
+ public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
final String key = notification.getKey();
@@ -1474,7 +1441,7 @@
} else {
if (shouldInterrupt && alertAgain) {
removeNotificationViews(key, ranking);
- addNotificationInternal(notification, ranking); //this will pop the headsup
+ addNotification(notification, ranking); //this will pop the headsup
} else {
updateNotificationViews(oldEntry, notification);
}
@@ -1514,7 +1481,7 @@
if (shouldInterrupt && alertAgain) {
if (DEBUG) Log.d(TAG, "reposting to invoke heads up for key: " + key);
removeNotificationViews(key, ranking);
- addNotificationInternal(notification, ranking); //this will pop the headsup
+ addNotification(notification, ranking); //this will pop the headsup
} else {
if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key);
removeNotificationViews(key, ranking);
@@ -1672,12 +1639,10 @@
mWindowManager.removeViewImmediate(mSearchPanelView);
}
mContext.unregisterReceiver(mBroadcastReceiver);
- if (USE_NOTIFICATION_LISTENER) {
- try {
- mNotificationListener.unregisterAsSystemService();
- } catch (RemoteException e) {
- // Ignore.
- }
+ try {
+ mNotificationListener.unregisterAsSystemService();
+ } catch (RemoteException e) {
+ // Ignore.
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index aaeadb6..2b61f09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -41,23 +41,20 @@
private static final int OP_REMOVE_ICON = 2;
private static final int MSG_ICON = 1 << MSG_SHIFT;
- private static final int MSG_ADD_NOTIFICATION = 2 << MSG_SHIFT;
- private static final int MSG_UPDATE_NOTIFICATION = 3 << MSG_SHIFT;
- private static final int MSG_REMOVE_NOTIFICATION = 4 << MSG_SHIFT;
- private static final int MSG_DISABLE = 5 << MSG_SHIFT;
- private static final int MSG_EXPAND_NOTIFICATIONS = 6 << MSG_SHIFT;
- private static final int MSG_COLLAPSE_PANELS = 7 << MSG_SHIFT;
- private static final int MSG_EXPAND_SETTINGS = 8 << MSG_SHIFT;
- private static final int MSG_SET_SYSTEMUI_VISIBILITY = 9 << MSG_SHIFT;
- private static final int MSG_TOP_APP_WINDOW_CHANGED = 10 << MSG_SHIFT;
- private static final int MSG_SHOW_IME_BUTTON = 11 << MSG_SHIFT;
- private static final int MSG_SET_HARD_KEYBOARD_STATUS = 12 << MSG_SHIFT;
- private static final int MSG_TOGGLE_RECENT_APPS = 13 << MSG_SHIFT;
- private static final int MSG_PRELOAD_RECENT_APPS = 14 << MSG_SHIFT;
- private static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 15 << MSG_SHIFT;
- private static final int MSG_SET_WINDOW_STATE = 16 << MSG_SHIFT;
- private static final int MSG_SHOW_RECENT_APPS = 17 << MSG_SHIFT;
- private static final int MSG_HIDE_RECENT_APPS = 18 << MSG_SHIFT;
+ private static final int MSG_DISABLE = 2 << MSG_SHIFT;
+ private static final int MSG_EXPAND_NOTIFICATIONS = 3 << MSG_SHIFT;
+ private static final int MSG_COLLAPSE_PANELS = 4 << MSG_SHIFT;
+ private static final int MSG_EXPAND_SETTINGS = 5 << MSG_SHIFT;
+ private static final int MSG_SET_SYSTEMUI_VISIBILITY = 6 << MSG_SHIFT;
+ private static final int MSG_TOP_APP_WINDOW_CHANGED = 7 << MSG_SHIFT;
+ private static final int MSG_SHOW_IME_BUTTON = 8 << MSG_SHIFT;
+ private static final int MSG_SET_HARD_KEYBOARD_STATUS = 9 << MSG_SHIFT;
+ private static final int MSG_TOGGLE_RECENT_APPS = 10 << MSG_SHIFT;
+ private static final int MSG_PRELOAD_RECENT_APPS = 11 << MSG_SHIFT;
+ private static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 12 << MSG_SHIFT;
+ private static final int MSG_SET_WINDOW_STATE = 13 << MSG_SHIFT;
+ private static final int MSG_SHOW_RECENT_APPS = 14 << MSG_SHIFT;
+ private static final int MSG_HIDE_RECENT_APPS = 15 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -80,9 +77,6 @@
public void updateIcon(String slot, int index, int viewIndex,
StatusBarIcon old, StatusBarIcon icon);
public void removeIcon(String slot, int index, int viewIndex);
- public void addNotification(StatusBarNotification notification);
- public void updateNotification(StatusBarNotification notification);
- public void removeNotification(String key);
public void disable(int state);
public void animateExpandNotificationsPanel();
public void animateCollapsePanels(int flags);
@@ -123,26 +117,6 @@
}
}
- @Override
- public void addNotification(StatusBarNotification notification) {
- synchronized (mList) {
- mHandler.obtainMessage(MSG_ADD_NOTIFICATION, 0, 0, notification).sendToTarget();
- }
- }
-
- @Override
- public void updateNotification(StatusBarNotification notification) {
- synchronized (mList) {
- mHandler.obtainMessage(MSG_UPDATE_NOTIFICATION, 0, 0, notification).sendToTarget();
- }
- }
-
- public void removeNotification(String key) {
- synchronized (mList) {
- mHandler.obtainMessage(MSG_REMOVE_NOTIFICATION, 0, 0, key).sendToTarget();
- }
- }
-
public void disable(int state) {
synchronized (mList) {
mHandler.removeMessages(MSG_DISABLE);
@@ -279,18 +253,6 @@
}
break;
}
- case MSG_ADD_NOTIFICATION: {
- mCallbacks.addNotification((StatusBarNotification) msg.obj);
- break;
- }
- case MSG_UPDATE_NOTIFICATION: {
- mCallbacks.updateNotification((StatusBarNotification) msg.obj);
- break;
- }
- case MSG_REMOVE_NOTIFICATION: {
- mCallbacks.removeNotification((String) msg.obj);
- break;
- }
case MSG_DISABLE:
mCallbacks.disable(msg.arg1);
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 4d4a8ab..c3fb83c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -167,6 +167,15 @@
}
/**
+ * Sets the notification as dark. The default implementation does nothing.
+ *
+ * @param dark Whether the notification should be dark.
+ * @param fade Whether an animation should be played to change the state.
+ */
+ public void setDark(boolean dark, boolean fade) {
+ }
+
+ /**
* @return The desired notification height.
*/
public int getIntrinsicHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java b/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java
deleted file mode 100644
index bfa74fa..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import android.app.Notification;
-import android.content.Context;
-import android.os.Process;
-import android.provider.Settings;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.view.View;
-
-import com.android.systemui.R;
-import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
-
-public class InterceptedNotifications {
- private static final String TAG = "InterceptedNotifications";
- private static final String SYNTHETIC_KEY = "InterceptedNotifications.SYNTHETIC_KEY";
-
- private final Context mContext;
- private final PhoneStatusBar mBar;
- private final ArrayMap<String, StatusBarNotification> mIntercepted
- = new ArrayMap<String, StatusBarNotification>();
- private final ArraySet<String> mReleased = new ArraySet<String>();
-
- private String mSynKey;
-
- public InterceptedNotifications(Context context, PhoneStatusBar bar) {
- mContext = context;
- mBar = bar;
- }
-
- public void releaseIntercepted() {
- final int n = mIntercepted.size();
- for (int i = 0; i < n; i++) {
- final StatusBarNotification sbn = mIntercepted.valueAt(i);
- mReleased.add(sbn.getKey());
- mBar.displayNotification(sbn, null);
- }
- mIntercepted.clear();
- updateSyntheticNotification();
- }
-
- public boolean tryIntercept(StatusBarNotification notification, RankingMap rankingMap) {
- if (rankingMap == null) return false;
- if (shouldDisplayIntercepted()) return false;
- if (mReleased.contains(notification.getKey())) return false;
- Ranking ranking = rankingMap.getRanking(notification.getKey());
- if (!ranking.isInterceptedByDoNotDisturb()) return false;
- mIntercepted.put(notification.getKey(), notification);
- updateSyntheticNotification();
- return true;
- }
-
- public void retryIntercepts(RankingMap ranking) {
- if (ranking == null) return;
-
- final int N = mIntercepted.size();
- final ArraySet<String> removed = new ArraySet<String>(N);
- for (int i = 0; i < N; i++) {
- final StatusBarNotification sbn = mIntercepted.valueAt(i);
- if (!tryIntercept(sbn, ranking)) {
- removed.add(sbn.getKey());
- mBar.displayNotification(sbn, ranking);
- }
- }
- if (!removed.isEmpty()) {
- mIntercepted.removeAll(removed);
- updateSyntheticNotification();
- }
- }
-
- public void remove(String key) {
- if (mIntercepted.remove(key) != null) {
- updateSyntheticNotification();
- }
- mReleased.remove(key);
- }
-
- public boolean isSyntheticEntry(Entry ent) {
- return ent.key.equals(mSynKey);
- }
-
- private boolean shouldDisplayIntercepted() {
- return Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.DISPLAY_INTERCEPTED_NOTIFICATIONS, 0) != 0;
- }
-
- private void updateSyntheticNotification() {
- if (mIntercepted.isEmpty()) {
- if (mSynKey != null) {
- mBar.removeNotificationInternal(mSynKey, null);
- mSynKey = null;
- }
- return;
- }
- final Notification n = new Notification.Builder(mContext)
- .setSmallIcon(R.drawable.ic_notify_zen)
- .setContentTitle(mContext.getResources().getQuantityString(
- R.plurals.zen_mode_notification_title,
- mIntercepted.size(), mIntercepted.size()))
- .setContentText(mContext.getString(R.string.zen_mode_notification_text))
- .setOngoing(true)
- .build();
- final StatusBarNotification sbn = new StatusBarNotification(mContext.getPackageName(),
- mContext.getBasePackageName(),
- TAG.hashCode(), TAG, Process.myUid(), Process.myPid(), 0, n,
- mBar.getCurrentUserHandle());
- if (mSynKey == null) {
- mSynKey = sbn.getKey();
- mBar.displayNotification(sbn, null);
- } else {
- mBar.updateNotificationInternal(sbn, null);
- }
- final NotificationData.Entry entry = mBar.mNotificationData.findByKey(mSynKey);
- entry.row.setOnClickListener(mSynClickListener);
- }
-
- private final View.OnClickListener mSynClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- releaseIntercepted();
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index a6ce5d5..cec7592 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -109,7 +109,6 @@
new KeyguardClockPositionAlgorithm();
private KeyguardClockPositionAlgorithm.Result mClockPositionResult =
new KeyguardClockPositionAlgorithm.Result();
- private boolean mIsSwipedHorizontally;
private boolean mIsExpanding;
private boolean mBlockTouches;
@@ -122,18 +121,7 @@
}
public void setStatusBar(PhoneStatusBar bar) {
- if (mStatusBar != null) {
- mStatusBar.setOnFlipRunnable(null);
- }
mStatusBar = bar;
- if (bar != null) {
- mStatusBar.setOnFlipRunnable(new Runnable() {
- @Override
- public void run() {
- requestPanelHeightUpdate();
- }
- });
- }
}
@Override
@@ -302,6 +290,8 @@
mUnlockIconActive = false;
mPageSwiper.reset();
closeQs();
+ mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
+ true /* cancelAnimators */);
}
public void closeQs() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index b94f6f3..b4faaaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -188,10 +188,10 @@
pv.collapse();
waiting = true;
} else {
+ pv.resetViews();
pv.setExpandedFraction(0); // just in case
pv.setVisibility(View.GONE);
pv.cancelPeek();
- pv.resetViews();
}
}
if (DEBUG) LOG("collapseAllPanels: animate=%s waiting=%s", animate, waiting);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 7016c0c..62ac54e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -64,7 +64,6 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.Settings.Global;
import android.provider.Settings.SettingNotFoundException;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
@@ -100,6 +99,7 @@
import com.android.systemui.DemoMode;
import com.android.systemui.EventLogTags;
import com.android.systemui.R;
+import com.android.systemui.doze.DozeService;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.qs.CircularClipper;
import com.android.systemui.qs.QSPanel;
@@ -110,7 +110,6 @@
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.GestureRecorder;
-import com.android.systemui.statusbar.InterceptedNotifications;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
@@ -169,11 +168,6 @@
private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; // see NotificationManagerService
private static final int HIDE_ICONS_BELOW_SCORE = Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER;
- /**
- * Default value of {@link android.provider.Settings.Global#LOCK_SCREEN_SHOW_NOTIFICATIONS}.
- */
- private static final boolean ALLOW_NOTIFICATIONS_DEFAULT = false;
-
private static final int STATUS_OR_NAV_TRANSIENT =
View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
private static final long AUTOHIDE_TIMEOUT_MS = 3000;
@@ -186,21 +180,6 @@
*/
private static final int HINT_RESET_DELAY_MS = 1200;
- // fling gesture tuning parameters, scaled to display density
- private float mSelfExpandVelocityPx; // classic value: 2000px/s
- private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up")
- private float mFlingExpandMinVelocityPx; // classic value: 200px/s
- private float mFlingCollapseMinVelocityPx; // classic value: 200px/s
- private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1)
- private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand)
- private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s
-
- private float mExpandAccelPx; // classic value: 2000px/s/s
- private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up")
-
- private float mFlingGestureMaxOutputVelocityPx; // how fast can it really go? (should be a little
- // faster than mSelfCollapseVelocityPx)
-
PhoneStatusBarPolicy mIconPolicy;
// These are no longer handled by the policy, because we need custom strategies for them
@@ -226,6 +205,7 @@
private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
private StatusBarWindowManager mStatusBarWindowManager;
private UnlockMethodCache mUnlockMethodCache;
+ private DozeServiceHost mDozeServiceHost;
int mPixelFormat;
Object mQueueLock = new Object();
@@ -398,15 +378,9 @@
}
}};
- private Runnable mOnFlipRunnable;
- private InterceptedNotifications mIntercepted;
- private VelocityTracker mSettingsTracker;
- private float mSettingsDownY;
- private boolean mSettingsStarted;
- private boolean mSettingsCancelled;
- private boolean mSettingsClosing;
private boolean mVisible;
private boolean mWaitingForKeyguardExit;
+ private boolean mDozing;
private Interpolator mLinearOutSlowIn;
private Interpolator mAlphaOut = new PathInterpolator(0f, 0.4f, 1f, 1f);
@@ -422,14 +396,12 @@
private int mDisabledUnmodified;
- public void setOnFlipRunnable(Runnable onFlipRunnable) {
- mOnFlipRunnable = onFlipRunnable;
- }
-
/** Keys of notifications currently visible to the user. */
private final ArraySet<String> mCurrentlyVisibleNotifications = new ArraySet<String>();
private long mLastVisibilityReportUptimeMs;
+ private final ShadeUpdates mShadeUpdates = new ShadeUpdates();
+
private static final int VISIBLE_LOCATIONS = ViewState.LOCATION_FIRST_CARD
| ViewState.LOCATION_TOP_STACK_PEEKING
| ViewState.LOCATION_MAIN_AREA
@@ -510,19 +482,6 @@
};
@Override
- public void setZenMode(int mode) {
- super.setZenMode(mode);
- if (!isDeviceProvisioned()) return;
- final boolean zen = mode != Settings.Global.ZEN_MODE_OFF;
- if (!zen) {
- mIntercepted.releaseIntercepted();
- }
- if (mIconPolicy != null) {
- mIconPolicy.setZenMode(zen);
- }
- }
-
- @Override
protected void setShowLockscreenNotifications(boolean show) {
super.setShowLockscreenNotifications(show);
updateStackScrollerState();
@@ -533,7 +492,6 @@
mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
updateDisplaySize();
- mIntercepted = new InterceptedNotifications(mContext, this);
super.start(); // calls createAndAddWindows()
addNavigationBar();
@@ -553,6 +511,9 @@
}
mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
startKeyguard();
+
+ mDozeServiceHost = new DozeServiceHost();
+ putComponent(DozeService.Host.class, mDozeServiceHost);
}
// ================================================================================
@@ -1072,18 +1033,8 @@
}
@Override
- public void addNotificationInternal(StatusBarNotification notification, RankingMap ranking) {
+ public void addNotification(StatusBarNotification notification, RankingMap ranking) {
if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
- if (mZenMode != Global.ZEN_MODE_OFF && mIntercepted.tryIntercept(notification, ranking)) {
- // Forward the ranking so we can sort the new notification.
- mNotificationData.updateRanking(ranking);
- return;
- }
- mIntercepted.remove(notification.getKey());
- displayNotification(notification, ranking);
- }
-
- public void displayNotification(StatusBarNotification notification, RankingMap ranking) {
if (mUseHeadsUp && shouldInterrupt(notification)) {
if (DEBUG) Log.d(TAG, "launching notification in heads up mode");
Entry interruptionCandidate = new Entry(notification, null);
@@ -1165,21 +1116,13 @@
}
@Override
- public void updateNotificationInternal(StatusBarNotification notification, RankingMap ranking) {
- super.updateNotificationInternal(notification, ranking);
- // if we're here, then the notification is already in the shade
- mIntercepted.remove(notification.getKey());
- }
-
- @Override
- protected void updateRankingInternal(RankingMap ranking) {
+ protected void updateNotificationRanking(RankingMap ranking) {
mNotificationData.updateRanking(ranking);
- mIntercepted.retryIntercepts(ranking);
updateNotifications();
}
@Override
- public void removeNotificationInternal(String key, RankingMap ranking) {
+ public void removeNotification(String key, RankingMap ranking) {
if (ENABLE_HEADS_UP && mHeadsUpNotificationView.getEntry() != null
&& key.equals(mHeadsUpNotificationView.getEntry().notification.getKey())) {
mHeadsUpNotificationView.clear();
@@ -1202,7 +1145,6 @@
animateCollapsePanels();
}
}
- mIntercepted.remove(key);
setAreThereNotifications();
}
@@ -1267,7 +1209,6 @@
for (View remove : toRemove) {
mStackScroller.removeView(remove);
}
-
for (int i=0; i<toShow.size(); i++) {
View v = toShow.get(i);
if (v.getParent() == null) {
@@ -1300,6 +1241,7 @@
updateRowStates();
updateSpeedbump();
mNotificationPanel.setQsExpansionEnabled(provisioned && mUserSetup);
+ mShadeUpdates.check();
}
private void updateSpeedbump() {
@@ -1358,9 +1300,6 @@
// in "public" mode (atop a secure keyguard), secret notifs are totally hidden
continue;
}
- if (mIntercepted.isSyntheticEntry(ent)) {
- continue;
- }
toShow.add(ent.icon);
}
@@ -1665,10 +1604,6 @@
}
}
- private Handler getHandler() {
- return mHandler;
- }
-
View.OnFocusChangeListener mFocusChangeListener = new View.OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
// Because 'v' is a ViewGroup, all its children will be (un)selected
@@ -1813,12 +1748,6 @@
if (false) postStartTracing();
}
- private static void cancelAnim(Animator anim) {
- if (anim != null) {
- anim.cancel();
- }
- }
-
public void flipToNotifications(boolean animate) {
// TODO: Animation
mNotificationPanel.closeQs();
@@ -2311,6 +2240,7 @@
pw.println(windowStateToString(mStatusBarWindowState));
pw.print(" mStatusBarMode=");
pw.println(BarTransitions.modeToString(mStatusBarMode));
+ pw.print(" mDozing="); pw.println(mDozing);
pw.print(" mZenMode=");
pw.println(Settings.Global.zenModeToString(mZenMode));
pw.print(" mUseHeadsUp=");
@@ -2445,28 +2375,6 @@
}
}
- private View.OnClickListener mClearButtonListener = new View.OnClickListener() {
- public void onClick(View v) {
- synchronized (mNotificationData) {
- mPostCollapseCleanup = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) {
- Log.v(TAG, "running post-collapse cleanup");
- }
- try {
- mBarService.onClearAllNotifications(mCurrentUserId);
- } catch (Exception ex) { }
- }
- };
-
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
- return;
- // TODO: Handle this better with notification stack scroller
- }
- }
- };
-
public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned) {
if (onlyProvisioned && !isDeviceProvisioned()) return;
@@ -2494,12 +2402,6 @@
}
};
- private View.OnClickListener mNotificationButtonListener = new View.OnClickListener() {
- public void onClick(View v) {
- animateExpandNotificationsPanel();
- }
- };
-
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.v(TAG, "onReceive: " + intent);
@@ -2633,21 +2535,6 @@
mEdgeBorder = res.getDimensionPixelSize(R.dimen.status_bar_edge_ignore);
- mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity);
- mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity);
- mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity);
- mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity);
-
- mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1);
- mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1);
-
- mExpandAccelPx = res.getDimension(R.dimen.expand_accel);
- mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel);
-
- mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity);
-
- mFlingGestureMaxOutputVelocityPx = res.getDimension(R.dimen.fling_gesture_max_output_velocity);
-
mNotificationPanelGravity = res.getInteger(R.integer.notification_panel_layout_gravity);
if (mNotificationPanelGravity <= 0) {
mNotificationPanelGravity = Gravity.START | Gravity.TOP;
@@ -2713,7 +2600,6 @@
if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
return;
}
-
String[] newlyVisibleAr = newlyVisible.toArray(new String[newlyVisible.size()]);
String[] noLongerVisibleAr = noLongerVisible.toArray(new String[noLongerVisible.size()]);
try {
@@ -2988,6 +2874,7 @@
mNotificationPanel.setKeyguardShowing(false);
mScrimController.setKeyguardShowing(false);
}
+ updateDozingState();
updateStackScrollerState();
updatePublicMode();
updateNotifications();
@@ -2995,6 +2882,26 @@
updateCarrierLabelVisibility(false);
}
+ private void updateDozingState() {
+ final boolean bottomGone = mKeyguardBottomArea.getVisibility() == View.GONE;
+ if (mDozing) {
+ mNotificationPanel.setBackgroundColor(0xff000000);
+ mHeader.setVisibility(View.INVISIBLE);
+ if (!bottomGone) {
+ mKeyguardBottomArea.setVisibility(View.INVISIBLE);
+ }
+ mStackScroller.setDark(true, false /*animate*/);
+ } else {
+ mNotificationPanel.setBackground(null);
+ mHeader.setVisibility(View.VISIBLE);
+ if (!bottomGone) {
+ mKeyguardBottomArea.setVisibility(View.VISIBLE);
+ }
+ mStackScroller.setDark(false, false /*animate*/);
+ }
+ mScrimController.setDozing(mDozing);
+ }
+
public void updateStackScrollerState() {
if (mStackScroller == null) return;
boolean onKeyguard = mState == StatusBarState.KEYGUARD;
@@ -3267,4 +3174,121 @@
}
notifyUiVisibilityChanged(mSystemUiVisibility);
}
+
+ private final class ShadeUpdates {
+ private final ArraySet<String> mVisibleNotifications = new ArraySet<String>();
+ private final ArraySet<String> mNewVisibleNotifications = new ArraySet<String>();
+
+ public void check() {
+ mNewVisibleNotifications.clear();
+ for (int i = 0; i < mNotificationData.size(); i++) {
+ final Entry entry = mNotificationData.get(i);
+ final boolean visible = entry.row != null
+ && entry.row.getVisibility() == View.VISIBLE;
+ if (visible) {
+ mNewVisibleNotifications.add(entry.key + entry.notification.getPostTime());
+ }
+ }
+ final boolean updates = !mVisibleNotifications.containsAll(mNewVisibleNotifications);
+ mVisibleNotifications.clear();
+ mVisibleNotifications.putAll(mNewVisibleNotifications);
+
+ // We have new notifications
+ if (updates && mDozeServiceHost != null) {
+ mDozeServiceHost.fireNewNotifications();
+ }
+ }
+ }
+
+ private final class DozeServiceHost implements DozeService.Host {
+ // Amount of time to allow to update the time shown on the screen before releasing
+ // the wakelock. This timeout is design to compensate for the fact that we don't
+ // currently have a way to know when time display contents have actually been
+ // refreshed once we've finished rendering a new frame.
+ private static final long PROCESSING_TIME = 500;
+
+ private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+ private final H mHandler = new H();
+
+ private DozeService mCurrentDozeService;
+
+ public void fireNewNotifications() {
+ for (Callback callback : mCallbacks) {
+ callback.onNewNotifications();
+ }
+ }
+
+ @Override
+ public void addCallback(Callback callback) {
+ mCallbacks.add(callback);
+ }
+
+ @Override
+ public void removeCallback(Callback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ @Override
+ public void requestDoze(DozeService dozeService) {
+ if (dozeService == null) return;
+ dozeService.stayAwake(PROCESSING_TIME);
+ mHandler.obtainMessage(H.REQUEST_DOZE, dozeService).sendToTarget();
+ }
+
+ @Override
+ public void requestTease(DozeService dozeService) {
+ if (dozeService == null) return;
+ dozeService.stayAwake(PROCESSING_TIME);
+ mHandler.obtainMessage(H.REQUEST_TEASE, dozeService).sendToTarget();
+ }
+
+ @Override
+ public void dozingStopped(DozeService dozeService) {
+ if (dozeService == null) return;
+ dozeService.stayAwake(PROCESSING_TIME);
+ mHandler.obtainMessage(H.DOZING_STOPPED, dozeService).sendToTarget();
+ }
+
+ private void handleRequestDoze(DozeService dozeService) {
+ mCurrentDozeService = dozeService;
+ if (!mDozing) {
+ mDozing = true;
+ updateDozingState();
+ }
+ mCurrentDozeService.startDozing();
+ }
+
+ private void handleRequestTease(DozeService dozeService) {
+ if (!dozeService.equals(mCurrentDozeService)) return;
+ final long stayAwake = mScrimController.tease();
+ mCurrentDozeService.stayAwake(stayAwake);
+ }
+
+ private void handleDozingStopped(DozeService dozeService) {
+ if (dozeService.equals(mCurrentDozeService)) {
+ mCurrentDozeService = null;
+ }
+ if (mDozing) {
+ mDozing = false;
+ updateDozingState();
+ }
+ }
+
+ private final class H extends Handler {
+ private static final int REQUEST_DOZE = 1;
+ private static final int REQUEST_TEASE = 2;
+ private static final int DOZING_STOPPED = 3;
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == REQUEST_DOZE) {
+ handleRequestDoze((DozeService) msg.obj);
+ } else if (msg.what == REQUEST_TEASE) {
+ handleRequestTease((DozeService) msg.obj);
+ } else if (msg.what == DOZING_STOPPED) {
+ handleDozingStopped((DozeService) msg.obj);
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 1264d75..bf63f7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -21,21 +21,35 @@
import android.animation.ValueAnimator;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
+import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
+import com.android.systemui.R;
+
/**
* Controls both the scrim behind the notifications and in front of the notifications (when a
* security method gets shown).
*/
public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
+ private static final String TAG = "ScrimController";
+ private static final boolean DEBUG = false;
private static final float SCRIM_BEHIND_ALPHA = 0.62f;
private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.5f;
private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
private static final long ANIMATION_DURATION = 220;
+ private static final int TAG_KEY_ANIM = R.id.scrim;
+
+ private static final int NUM_TEASES = 3;
+ private static final long TEASE_IN_ANIMATION_DURATION = 500;
+ private static final long TEASE_VISIBLE_DURATION = 3000;
+ private static final long TEASE_OUT_ANIMATION_DURATION = 1000;
+ private static final long TEASE_INVISIBLE_DURATION = 1000;
+ private static final long TEASE_DURATION = TEASE_IN_ANIMATION_DURATION
+ + TEASE_VISIBLE_DURATION + TEASE_OUT_ANIMATION_DURATION + TEASE_INVISIBLE_DURATION;
private final View mScrimBehind;
private final View mScrimInFront;
@@ -54,6 +68,8 @@
private long mAnimationDelay;
private Runnable mOnAnimationFinished;
private boolean mAnimationStarted;
+ private boolean mDozing;
+ private int mTeasesRemaining;
private final Interpolator mInterpolator = new DecelerateInterpolator();
@@ -97,6 +113,29 @@
scheduleUpdate();
}
+ public void setDozing(boolean dozing) {
+ if (mDozing == dozing) return;
+ mDozing = dozing;
+ if (!mDozing) {
+ cancelTeasing();
+ }
+ scheduleUpdate();
+ }
+
+ /** When dozing, fade screen contents in and out a few times using the front scrim. */
+ public long tease() {
+ if (!mDozing) return 0;
+ mTeasesRemaining = NUM_TEASES;
+ mScrimInFront.post(mTeaseIn);
+ return NUM_TEASES * TEASE_DURATION;
+ }
+
+ private void cancelTeasing() {
+ mTeasesRemaining = 0;
+ mScrimInFront.removeCallbacks(mTeaseIn);
+ mScrimInFront.removeCallbacks(mTeaseOut);
+ }
+
private void scheduleUpdate() {
if (mUpdatePending) return;
@@ -125,6 +164,8 @@
} else if (mBouncerShowing) {
setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA);
setScrimBehindColor(0f);
+ } else if (mDozing) {
+ setScrimInFrontColor(1);
} else {
setScrimInFrontColor(0f);
setScrimBehindColor(SCRIM_BEHIND_ALPHA_KEYGUARD);
@@ -174,6 +215,10 @@
if (current == targetColor) {
return;
}
+ Object runningAnim = scrim.getTag(TAG_KEY_ANIM);
+ if (runningAnim instanceof ValueAnimator) {
+ ((ValueAnimator) runningAnim).cancel();
+ }
ValueAnimator anim = ValueAnimator.ofInt(current, target);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
@@ -193,9 +238,11 @@
mOnAnimationFinished.run();
mOnAnimationFinished = null;
}
+ scrim.setTag(TAG_KEY_ANIM, null);
}
});
anim.start();
+ scrim.setTag(TAG_KEY_ANIM, anim);
mAnimationStarted = true;
}
@@ -225,4 +272,51 @@
mAnimationStarted = false;
return true;
}
+
+ private final Runnable mTeaseIn = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Tease in, mDozing=" + mDozing
+ + " mTeasesRemaining=" + mTeasesRemaining);
+ if (!mDozing || mTeasesRemaining == 0) return;
+ mTeasesRemaining--;
+ mDurationOverride = TEASE_IN_ANIMATION_DURATION;
+ mAnimationDelay = 0;
+ mAnimateChange = true;
+ mOnAnimationFinished = mTeaseInFinished;
+ setScrimColor(mScrimInFront, 0);
+ }
+ };
+
+ private final Runnable mTeaseInFinished = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Tease in finished, mDozing=" + mDozing);
+ if (!mDozing) return;
+ mScrimInFront.postDelayed(mTeaseOut, TEASE_VISIBLE_DURATION);
+ }
+ };
+
+ private final Runnable mTeaseOut = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Tease in finished, mDozing=" + mDozing);
+ if (!mDozing) return;
+ mDurationOverride = TEASE_OUT_ANIMATION_DURATION;
+ mAnimationDelay = 0;
+ mAnimateChange = true;
+ mOnAnimationFinished = mTeaseOutFinished;
+ setScrimColor(mScrimInFront, 1);
+ }
+ };
+
+ private final Runnable mTeaseOutFinished = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Tease out finished, mTeasesRemaining=" + mTeasesRemaining);
+ if (mTeasesRemaining > 0) {
+ mScrimInFront.postDelayed(mTeaseIn, TEASE_INVISIBLE_DURATION);
+ }
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index fcc951e..0582140 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -33,6 +33,7 @@
private float mOverScrollBottomAmount;
private int mSpeedBumpIndex = -1;
private float mScrimAmount;
+ private boolean mDark;
public int getScrollY() {
return mScrollY;
@@ -62,6 +63,11 @@
mDimmed = dimmed;
}
+ /** In dark mode, we draw as little as possible, assuming a black background */
+ public void setDark(boolean dark) {
+ mDark = dark;
+ }
+
/**
* In dimmed mode, a child can be activated, which happens on the first tap of the double-tap
* interaction. This child is then scaled normally and its background is fully opaque.
@@ -74,6 +80,10 @@
return mDimmed;
}
+ public boolean isDark() {
+ return mDark;
+ }
+
public ActivatableNotificationView getActivatedChild() {
return mActivatedChild;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
index cf56fa57..99d3a01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
@@ -29,6 +29,7 @@
boolean animateHeight;
boolean animateTopInset;
boolean animateDimmed;
+ boolean animateDark;
boolean hasDelays;
public AnimationFilter animateAlpha() {
@@ -71,6 +72,11 @@
return this;
}
+ public AnimationFilter animateDark() {
+ animateDark = true;
+ return this;
+ }
+
/**
* Combines multiple filters into {@code this} filter, using or as the operand .
*
@@ -92,6 +98,7 @@
animateHeight |= filter.animateHeight;
animateTopInset |= filter.animateTopInset;
animateDimmed |= filter.animateDimmed;
+ animateDark |= filter.animateDark;
hasDelays |= filter.hasDelays;
}
@@ -103,6 +110,7 @@
animateHeight = false;
animateTopInset = false;
animateDimmed = false;
+ animateDark = false;
hasDelays = false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index f6e9aef..4220efe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -128,6 +128,7 @@
private boolean mNeedsAnimation;
private boolean mTopPaddingNeedsAnimation;
private boolean mDimmedNeedsAnimation;
+ private boolean mDarkNeedsAnimation;
private boolean mActivateNeedsAnimation;
private boolean mIsExpanded = true;
private boolean mChildrenUpdateRequested;
@@ -356,14 +357,6 @@
return getNotGoneChildCount() > 1;
}
- private boolean isViewExpanded(View view) {
- if (view != null) {
- ExpandableView expandView = (ExpandableView) view;
- return expandView.getActualHeight() > mCollapsedSize;
- }
- return false;
- }
-
/**
* Updates the children views according to the stack scroll algorithm. Call this whenever
* modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
@@ -1479,6 +1472,7 @@
generateTopPaddingEvent();
generateActivateEvent();
generateDimmedEvent();
+ generateDarkEvent();
mNeedsAnimation = false;
}
@@ -1554,6 +1548,14 @@
mDimmedNeedsAnimation = false;
}
+ private void generateDarkEvent() {
+ if (mDarkNeedsAnimation) {
+ mAnimationEvents.add(
+ new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK));
+ }
+ mDarkNeedsAnimation = false;
+ }
+
private boolean onInterceptTouchEventScroll(MotionEvent ev) {
if (!isScrollingEnabled()) {
return false;
@@ -1852,6 +1854,18 @@
}
/**
+ * See {@link AmbientState#setDark}.
+ */
+ public void setDark(boolean dark, boolean animate) {
+ mAmbientState.setDark(dark);
+ if (animate && mAnimationsEnabled) {
+ mDarkNeedsAnimation = true;
+ mNeedsAnimation = true;
+ }
+ requestChildrenUpdate();
+ }
+
+ /**
* A listener that is notified when some child locations might have changed.
*/
public interface OnChildLocationsChangedListener {
@@ -1940,7 +1954,11 @@
.animateHeight()
.animateTopInset()
.animateY()
- .animateZ()
+ .animateZ(),
+
+ // ANIMATION_TYPE_DARK
+ new AnimationFilter()
+ .animateDark(),
};
static int[] LENGTHS = new int[] {
@@ -1971,6 +1989,9 @@
// ANIMATION_TYPE_CHANGE_POSITION
StackStateAnimator.ANIMATION_DURATION_STANDARD,
+
+ // ANIMATION_TYPE_DARK
+ StackStateAnimator.ANIMATION_DURATION_STANDARD,
};
static final int ANIMATION_TYPE_ADD = 0;
@@ -1982,6 +2003,7 @@
static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
static final int ANIMATION_TYPE_DIMMED = 7;
static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
+ static final int ANIMATION_TYPE_DARK = 9;
final long eventStartTime;
final View changingView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 9a4b798..4956fe8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -233,12 +233,14 @@
private void updateDimmedActivated(AmbientState ambientState, StackScrollState resultState,
StackScrollAlgorithmState algorithmState) {
boolean dimmed = ambientState.isDimmed();
+ boolean dark = ambientState.isDark();
View activatedChild = ambientState.getActivatedChild();
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
childViewState.dimmed = dimmed;
+ childViewState.dark = dark;
boolean isActivatedChild = activatedChild == child;
childViewState.scale = !dimmed || isActivatedChild
? 1.0f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 02f2cd6..d8407d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -148,6 +148,9 @@
// apply dimming
child.setDimmed(state.dimmed, false /* animate */);
+ // apply dark
+ child.setDark(state.dark, false /* animate */);
+
// apply scrimming
child.setScrimAmount(state.scrimAmount);
@@ -224,6 +227,7 @@
boolean gone;
float scale;
boolean dimmed;
+ boolean dark;
/**
* A value between 0 and 1 indicating how much the view should be scrimmed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 0006dad..5efbc99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -176,6 +176,9 @@
// start dimmed animation
child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
+ // start dark animation
+ child.setDark(viewState.dark, mAnimationFilter.animateDark);
+
// apply scrimming
child.setScrimAmount(viewState.scrimAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 9260aac..e354166 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -47,27 +47,15 @@
}
@Override
- public void addNotification(StatusBarNotification notification) {
+ public void addNotification(StatusBarNotification notification, RankingMap ranking) {
}
@Override
- public void addNotificationInternal(StatusBarNotification notification, RankingMap ranking) {
+ protected void updateNotificationRanking(RankingMap ranking) {
}
@Override
- protected void updateRankingInternal(RankingMap ranking) {
- }
-
- @Override
- public void updateNotification(StatusBarNotification notification) {
- }
-
- @Override
- public void removeNotificationInternal(String key, RankingMap ranking) {
- }
-
- @Override
- public void removeNotification(String key) {
+ public void removeNotification(String key, RankingMap ranking) {
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index 1cab7ea..d514c99 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -34,6 +34,9 @@
import android.media.AudioSystem;
import android.media.RingtoneManager;
import android.media.ToneGenerator;
+import android.media.VolumeProvider;
+import android.media.session.MediaController;
+import android.media.session.MediaController.VolumeInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
@@ -224,6 +227,7 @@
/** Object that contains data for each slider */
private class StreamControl {
int streamType;
+ MediaController controller;
ViewGroup group;
ImageView icon;
SeekBar seekbarView;
@@ -405,7 +409,8 @@
if (streamType == STREAM_MASTER) {
return mAudioManager.isMasterMute();
} else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
- return (mAudioManager.getRemoteStreamVolume() <= 0);
+ // TODO do we need to support a distinct mute property for remote?
+ return false;
} else {
return mAudioManager.isStreamMute(streamType);
}
@@ -415,7 +420,14 @@
if (streamType == STREAM_MASTER) {
return mAudioManager.getMasterMaxVolume();
} else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
- return mAudioManager.getRemoteStreamMaxVolume();
+ if (mStreamControls != null) {
+ StreamControl sc = mStreamControls.get(streamType);
+ if (sc != null && sc.controller != null) {
+ VolumeInfo vi = sc.controller.getVolumeInfo();
+ return vi.getMaxVolume();
+ }
+ }
+ return -1;
} else {
return mAudioManager.getStreamMaxVolume(streamType);
}
@@ -425,19 +437,32 @@
if (streamType == STREAM_MASTER) {
return mAudioManager.getMasterVolume();
} else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
- return mAudioManager.getRemoteStreamVolume();
+ if (mStreamControls != null) {
+ StreamControl sc = mStreamControls.get(streamType);
+ if (sc != null && sc.controller != null) {
+ VolumeInfo vi = sc.controller.getVolumeInfo();
+ return vi.getCurrentVolume();
+ }
+ }
+ return -1;
} else {
return mAudioManager.getStreamVolume(streamType);
}
}
- private void setStreamVolume(int streamType, int index, int flags) {
- if (streamType == STREAM_MASTER) {
- mAudioManager.setMasterVolume(index, flags);
- } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
- mAudioManager.setRemoteStreamVolume(index);
- } else {
- mAudioManager.setStreamVolume(streamType, index, flags);
+ private void setStreamVolume(StreamControl sc, int index, int flags) {
+ if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
+ if (sc.controller != null) {
+ sc.controller.setVolumeTo(index, flags);
+ } else {
+ Log.wtf(mTag, "Adjusting remote volume without a controller!");
+ }
+ } else if (getStreamVolume(sc.streamType) != index) {
+ if (sc.streamType == STREAM_MASTER) {
+ mAudioManager.setMasterVolume(index, flags);
+ } else {
+ mAudioManager.setStreamVolume(sc.streamType, index, flags);
+ }
}
}
@@ -549,7 +574,7 @@
if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
// never disable touch interactions for remote playback, the muting is not tied to
// the state of the phone.
- sc.seekbarView.setEnabled(true);
+ sc.seekbarView.setEnabled(!fixedVolume);
} else if (fixedVolume ||
(sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
(sConfirmSafeVolumeDialog != null)) {
@@ -677,7 +702,7 @@
obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
}
- public void postRemoteVolumeChanged(int streamType, int flags) {
+ public void postRemoteVolumeChanged(MediaController controller, int flags) {
if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return;
synchronized (this) {
if (mStreamControls == null) {
@@ -685,7 +710,7 @@
}
}
removeMessages(MSG_FREE_RESOURCES);
- obtainMessage(MSG_REMOTE_VOLUME_CHANGED, streamType, flags).sendToTarget();
+ obtainMessage(MSG_REMOTE_VOLUME_CHANGED, flags, 0, controller).sendToTarget();
}
public void postRemoteSliderVisibility(boolean visible) {
@@ -758,7 +783,7 @@
if (mActiveStreamType != streamType) {
reorderSliders(streamType);
}
- onShowVolumeChanged(streamType, flags);
+ onShowVolumeChanged(streamType, flags, null);
}
}
@@ -790,7 +815,7 @@
onVolumeChanged(streamType, flags);
}
- protected void onShowVolumeChanged(int streamType, int flags) {
+ protected void onShowVolumeChanged(int streamType, int flags, MediaController controller) {
int index = getStreamVolume(streamType);
mRingIsSilent = false;
@@ -803,6 +828,7 @@
// get max volume for progress bar
int max = getStreamMaxVolume(streamType);
+ StreamControl sc = mStreamControls.get(streamType);
switch (streamType) {
@@ -865,13 +891,37 @@
}
case AudioService.STREAM_REMOTE_MUSIC: {
+ if (controller == null && sc != null) {
+ // If we weren't passed one try using the last one set.
+ controller = sc.controller;
+ }
+ if (controller == null) {
+ // We still don't have one, ignore the command.
+ Log.w(mTag, "sent remote volume change without a controller!");
+ } else {
+ VolumeInfo vi = controller.getVolumeInfo();
+ index = vi.getCurrentVolume();
+ max = vi.getMaxVolume();
+ if ((vi.getVolumeControl() & VolumeProvider.VOLUME_CONTROL_FIXED) != 0) {
+ // if the remote volume is fixed add the flag for the UI
+ flags |= AudioManager.FLAG_FIXED_VOLUME;
+ }
+ }
if (LOGD) { Log.d(mTag, "showing remote volume "+index+" over "+ max); }
break;
}
}
- StreamControl sc = mStreamControls.get(streamType);
if (sc != null) {
+ if (streamType == AudioService.STREAM_REMOTE_MUSIC && controller != sc.controller) {
+ if (sc.controller != null) {
+ sc.controller.removeCallback(mMediaControllerCb);
+ }
+ sc.controller = controller;
+ if (controller != null) {
+ sc.controller.addCallback(mMediaControllerCb);
+ }
+ }
if (sc.seekbarView.getMax() != max) {
sc.seekbarView.setMax(max);
}
@@ -949,34 +999,21 @@
mVibrator.vibrate(VIBRATE_DURATION, AudioManager.STREAM_SYSTEM);
}
- protected void onRemoteVolumeChanged(int streamType, int flags) {
- // streamType is the real stream type being affected, but for the UI sliders, we
- // refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real
- // stream type.
- if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")");
+ protected void onRemoteVolumeChanged(MediaController controller, int flags) {
+ if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(controller:" + controller + ", flags: " + flags
+ + ")");
if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || isShowing()) {
synchronized (this) {
if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) {
reorderSliders(AudioService.STREAM_REMOTE_MUSIC);
}
- onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags);
+ onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags, controller);
}
} else {
if (LOGD) Log.d(mTag, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI");
}
- if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
- removeMessages(MSG_PLAY_SOUND);
- sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
- }
-
- if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
- removeMessages(MSG_PLAY_SOUND);
- removeMessages(MSG_VIBRATE);
- onStopSounds();
- }
-
removeMessages(MSG_FREE_RESOURCES);
sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
resetTimeout();
@@ -987,10 +1024,24 @@
if (isShowing()
&& (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC)
&& (mStreamControls != null)) {
- onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0);
+ onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0, null);
}
}
+ /**
+ * Clear the current remote stream controller.
+ */
+ private void clearRemoteStreamController() {
+ if (mStreamControls != null) {
+ StreamControl sc = mStreamControls.get(AudioService.STREAM_REMOTE_MUSIC);
+ if (sc != null) {
+ if (sc.controller != null) {
+ sc.controller.removeCallback(mMediaControllerCb);
+ sc.controller = null;
+ }
+ }
+ }
+ }
/**
* Handler for MSG_SLIDER_VISIBILITY_CHANGED
@@ -1137,6 +1188,7 @@
if (isShowing()) {
if (mDialog != null) {
mDialog.dismiss();
+ clearRemoteStreamController();
mActiveStreamType = -1;
}
}
@@ -1155,7 +1207,7 @@
}
case MSG_REMOTE_VOLUME_CHANGED: {
- onRemoteVolumeChanged(msg.arg1, msg.arg2);
+ onRemoteVolumeChanged((MediaController) msg.obj, msg.arg1);
break;
}
@@ -1202,9 +1254,7 @@
final Object tag = seekBar.getTag();
if (fromUser && tag instanceof StreamControl) {
StreamControl sc = (StreamControl) tag;
- if (getStreamVolume(sc.streamType) != progress) {
- setStreamVolume(sc.streamType, progress, 0);
- }
+ setStreamVolume(sc, progress, 0);
}
resetTimeout();
}
@@ -1215,19 +1265,6 @@
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
- final Object tag = seekBar.getTag();
- if (tag instanceof StreamControl) {
- StreamControl sc = (StreamControl) tag;
- // Because remote volume updates are asynchronous, AudioService
- // might have received a new remote volume value since the
- // finger adjusted the slider. So when the progress of the
- // slider isn't being tracked anymore, adjust the slider to the
- // last "published" remote volume value, so the UI reflects the
- // actual volume.
- if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
- seekBar.setProgress(getStreamVolume(AudioService.STREAM_REMOTE_MUSIC));
- }
- }
}
};
@@ -1257,4 +1294,10 @@
postZenModeChanged(zen);
}
};
+
+ private final MediaController.Callback mMediaControllerCb = new MediaController.Callback() {
+ public void onVolumeInfoChanged(VolumeInfo info) {
+ onRemoteVolumeUpdateIfShown();
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 7da90d8..c1f92ff 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -5,7 +5,11 @@
import android.content.res.Resources;
import android.database.ContentObserver;
import android.media.AudioManager;
+import android.media.IRemoteVolumeController;
import android.media.IVolumeController;
+import android.media.session.ISessionController;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionManager;
import android.net.Uri;
import android.os.Handler;
import android.os.RemoteException;
@@ -42,12 +46,21 @@
private final Handler mHandler = new Handler();
private AudioManager mAudioManager;
+ private MediaSessionManager mMediaSessionManager;
private VolumeController mVolumeController;
+ private RemoteVolumeController mRemoteVolumeController;
+
+ private VolumePanel mDialogPanel;
+ private VolumePanel mPanel;
@Override
public void start() {
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- mVolumeController = new VolumeController(mContext);
+ mMediaSessionManager = (MediaSessionManager) mContext
+ .getSystemService(Context.MEDIA_SESSION_SERVICE);
+ initPanel();
+ mVolumeController = new VolumeController();
+ mRemoteVolumeController = new RemoteVolumeController();
putComponent(VolumeComponent.class, mVolumeController);
updateController();
mContext.getContentResolver().registerContentObserver(SETTING_URI, false, mObserver);
@@ -57,12 +70,32 @@
if (Settings.Global.getInt(mContext.getContentResolver(), SETTING, DEFAULT) != 0) {
Log.d(TAG, "Registering volume controller");
mAudioManager.setVolumeController(mVolumeController);
+ mMediaSessionManager.setRemoteVolumeController(mRemoteVolumeController);
} else {
Log.d(TAG, "Unregistering volume controller");
mAudioManager.setVolumeController(null);
+ mMediaSessionManager.setRemoteVolumeController(null);
}
}
+ private void initPanel() {
+ mPanel = new VolumePanel(mContext, null, new ZenModeControllerImpl(mContext, mHandler));
+ final int delay = mContext.getResources().getInteger(R.integer.feedback_start_delay);
+ mPanel.setZenModePanelCallback(new ZenModePanel.Callback() {
+ @Override
+ public void onMoreSettings() {
+ mHandler.removeCallbacks(mStartZenSettings);
+ mHandler.postDelayed(mStartZenSettings, delay);
+ }
+
+ @Override
+ public void onInteraction() {
+ mDialogPanel.resetTimeout();
+ }
+ });
+ mDialogPanel = mPanel;
+ }
+
private final ContentObserver mObserver = new ContentObserver(mHandler) {
public void onChange(boolean selfChange, Uri uri) {
if (SETTING_URI.equals(uri)) {
@@ -71,55 +104,18 @@
}
};
+ private final Runnable mStartZenSettings = new Runnable() {
+ @Override
+ public void run() {
+ mDialogPanel.postDismiss();
+ final Intent intent = ZenModePanel.ZEN_SETTINGS;
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ }
+ };
+
/** For now, simply host an unmodified base volume panel in this process. */
private final class VolumeController extends IVolumeController.Stub implements VolumeComponent {
- private final VolumePanel mDialogPanel;
- private VolumePanel mPanel;
-
- public VolumeController(Context context) {
- mPanel = new VolumePanel(context, null, new ZenModeControllerImpl(mContext, mHandler));
- final int delay = context.getResources().getInteger(R.integer.feedback_start_delay);
- mPanel.setZenModePanelCallback(new ZenModePanel.Callback() {
- @Override
- public void onMoreSettings() {
- mHandler.removeCallbacks(mStartZenSettings);
- mHandler.postDelayed(mStartZenSettings, delay);
- }
-
- @Override
- public void onInteraction() {
- mDialogPanel.resetTimeout();
- }
- });
- mDialogPanel = mPanel;
- }
-
- private final Runnable mStartZenSettings = new Runnable() {
- @Override
- public void run() {
- mDialogPanel.postDismiss();
- final Intent intent = ZenModePanel.ZEN_SETTINGS;
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
- }
- };
-
- @Override
- public void hasNewRemotePlaybackInfo() throws RemoteException {
- mPanel.postHasNewRemotePlaybackInfo();
- }
-
- @Override
- public void remoteVolumeChanged(int streamType, int flags)
- throws RemoteException {
- mPanel.postRemoteVolumeChanged(streamType, flags);
- }
-
- @Override
- public void remoteSliderVisibility(boolean visible)
- throws RemoteException {
- mPanel.postRemoteSliderVisibility(visible);
- }
@Override
public void displaySafeVolumeWarning(int flags) throws RemoteException {
@@ -163,4 +159,21 @@
mPanel = panel == null ? mDialogPanel : panel;
}
}
+
+ private final class RemoteVolumeController extends IRemoteVolumeController.Stub {
+
+ @Override
+ public void remoteVolumeChanged(ISessionController binder, int flags)
+ throws RemoteException {
+ MediaController controller = MediaController.fromBinder(binder);
+ mPanel.postRemoteVolumeChanged(controller, flags);
+ }
+
+ @Override
+ public void updateRemoteController(ISessionController session) throws RemoteException {
+ mPanel.postRemoteSliderVisibility(session != null);
+ // TODO stash default session in case the slider can be opened other
+ // than by remoteVolumeChanged.
+ }
+ }
}
diff --git a/policy/src/com/android/internal/policy/impl/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java
index 0c16b78..56a8f7c 100644
--- a/policy/src/com/android/internal/policy/impl/GlobalActions.java
+++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java
@@ -21,6 +21,7 @@
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
import android.app.ActivityManagerNative;
import android.app.AlertDialog;
@@ -64,6 +65,8 @@
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerInternal;
import android.view.WindowManagerPolicy.WindowManagerFuncs;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
@@ -94,6 +97,7 @@
private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
private static final String GLOBAL_ACTION_KEY_USERS = "users";
private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
+ private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
private final Context mContext;
private final WindowManagerFuncs mWindowManagerFuncs;
@@ -279,6 +283,8 @@
addUsersToMenu(mItems);
} else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
mItems.add(getSettingsAction());
+ } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey) && hasTrustAgents()) {
+ mItems.add(getLockdownAction());
} else {
Log.e(TAG, "Invalid global action key " + actionKey);
}
@@ -317,6 +323,11 @@
return dialog;
}
+ private boolean hasTrustAgents() {
+ // TODO: Add implementation.
+ return true;
+ }
+
private final class PowerAction extends SinglePressAction implements LongPressAction {
private PowerAction() {
super(com.android.internal.R.drawable.ic_lock_power_off,
@@ -419,6 +430,32 @@
};
}
+ private Action getLockdownAction() {
+ return new SinglePressAction(com.android.internal.R.drawable.ic_lock_lock,
+ R.string.global_action_lockdown) {
+
+ @Override
+ public void onPress() {
+ new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
+ try {
+ WindowManagerGlobal.getWindowManagerService().lockNow(null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while trying to lock device.", e);
+ }
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ };
+ }
+
private UserInfo getCurrentUser() {
try {
return ActivityManagerNative.getDefault().getCurrentUser();
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 813ba47..ddfa9c1 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -4109,6 +4109,9 @@
// If the key would be handled globally, just return the result, don't worry about special
// key processing.
if (mGlobalKeyManager.shouldHandleGlobalKey(keyCode, event)) {
+ if (isWakeKey) {
+ mPowerManager.wakeUp(event.getEventTime());
+ }
return result;
}
diff --git a/policy/src/com/android/internal/policy/impl/WindowOrientationListener.java b/policy/src/com/android/internal/policy/impl/WindowOrientationListener.java
index 2cc33b5f..704da33 100644
--- a/policy/src/com/android/internal/policy/impl/WindowOrientationListener.java
+++ b/policy/src/com/android/internal/policy/impl/WindowOrientationListener.java
@@ -193,7 +193,9 @@
pw.println(prefix + "mSensor=" + mSensor);
pw.println(prefix + "mRate=" + mRate);
- mSensorEventListener.dumpLocked(pw, prefix);
+ if (mSensorEventListener != null) {
+ mSensorEventListener.dumpLocked(pw, prefix);
+ }
}
}
diff --git a/rs/java/android/renderscript/ScriptC.java b/rs/java/android/renderscript/ScriptC.java
index 3176e28..64d21e4 100644
--- a/rs/java/android/renderscript/ScriptC.java
+++ b/rs/java/android/renderscript/ScriptC.java
@@ -44,7 +44,6 @@
* @param id
* @param rs
*
- * @hide
*/
protected ScriptC(long id, RenderScript rs) {
super(id, rs);
@@ -70,7 +69,6 @@
* Only intended for use by the generated derived classes.
*
* @param rs
- * @hide
*/
protected ScriptC(RenderScript rs, String resName, byte[] bitcode32, byte[] bitcode64) {
super(0, rs);
diff --git a/rs/java/android/renderscript/ScriptIntrinsic.java b/rs/java/android/renderscript/ScriptIntrinsic.java
index 8719e017..4edce84 100644
--- a/rs/java/android/renderscript/ScriptIntrinsic.java
+++ b/rs/java/android/renderscript/ScriptIntrinsic.java
@@ -27,5 +27,8 @@
public abstract class ScriptIntrinsic extends Script {
ScriptIntrinsic(long id, RenderScript rs) {
super(id, rs);
+ if (id == 0) {
+ throw new RSRuntimeException("Loading of ScriptIntrinsic failed.");
+ }
}
}
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index ae39b05..fb3b117 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -1051,7 +1051,7 @@
jint dimsLen = _env->GetArrayLength(dims) * sizeof(int);
jint *dimsPtr = _env->GetIntArrayElements(dims, NULL);
rsScriptSetVarVE((RsContext)con, (RsScript)script, slot, ptr, len, (RsElement)elem,
- (const size_t*) dimsPtr, dimsLen);
+ (const uint32_t*) dimsPtr, dimsLen);
_env->ReleaseByteArrayElements(data, ptr, JNI_ABORT);
_env->ReleaseIntArrayElements(dims, dimsPtr, JNI_ABORT);
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 00bda06..19fff55 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -45,7 +45,6 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.bluetooth.BluetoothTetheringDataTracker;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
@@ -410,7 +409,8 @@
/**
* used to remove a network request, either a listener or a real request
- * includes a NetworkRequest
+ * arg1 = UID of caller
+ * obj = NetworkRequest
*/
private static final int EVENT_RELEASE_NETWORK_REQUEST = 22;
@@ -911,8 +911,6 @@
switch (config.radio) {
case TYPE_DUMMY:
return new DummyDataStateTracker(targetNetworkType, config.name);
- case TYPE_BLUETOOTH:
- return BluetoothTetheringDataTracker.getInstance();
case TYPE_WIMAX:
return makeWimaxStateTracker(mContext, mTrackerHandler);
case TYPE_PROXY:
@@ -3336,10 +3334,15 @@
}
}
- private void handleReleaseNetworkRequest(NetworkRequest request) {
- if (DBG) log("releasing NetworkRequest " + request);
- NetworkRequestInfo nri = mNetworkRequests.remove(request);
+ private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
+ NetworkRequestInfo nri = mNetworkRequests.get(request);
if (nri != null) {
+ if (nri.mUid != callingUid) {
+ if (DBG) log("Attempt to release unowned NetworkRequest " + request);
+ return;
+ }
+ if (DBG) log("releasing NetworkRequest " + request);
+ mNetworkRequests.remove(request);
// tell the network currently servicing this that it's no longer interested
NetworkAgentInfo affectedNetwork = mNetworkForRequestId.get(nri.request.requestId);
if (affectedNetwork != null) {
@@ -3485,7 +3488,7 @@
break;
}
case EVENT_RELEASE_NETWORK_REQUEST: {
- handleReleaseNetworkRequest((NetworkRequest) msg.obj);
+ handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1);
break;
}
}
@@ -4713,7 +4716,7 @@
LinkProperties lp = mCs.getLinkPropertiesForTypeInternal(
ConnectivityManager.TYPE_MOBILE_HIPRI);
boolean linkHasIpv4 = lp.hasIPv4Address();
- boolean linkHasIpv6 = lp.hasIPv6Address();
+ boolean linkHasIpv6 = lp.hasGlobalIPv6Address();
log("isMobileOk: linkHasIpv4=" + linkHasIpv4
+ " linkHasIpv6=" + linkHasIpv6);
@@ -5463,8 +5466,8 @@
@Override
public void releaseNetworkRequest(NetworkRequest networkRequest) {
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST,
- networkRequest));
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST, getCallingUid(),
+ 0, networkRequest));
}
@Override
diff --git a/services/core/java/com/android/server/DisplayThread.java b/services/core/java/com/android/server/DisplayThread.java
index 528ba0a..aa0a805 100644
--- a/services/core/java/com/android/server/DisplayThread.java
+++ b/services/core/java/com/android/server/DisplayThread.java
@@ -41,14 +41,14 @@
}
public static DisplayThread get() {
- synchronized (UiThread.class) {
+ synchronized (DisplayThread.class) {
ensureThreadLocked();
return sInstance;
}
}
public static Handler getHandler() {
- synchronized (UiThread.class) {
+ synchronized (DisplayThread.class) {
ensureThreadLocked();
return sHandler;
}
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 512ebc6..1b71518 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -23,6 +23,7 @@
import android.net.INetworkScoreCache;
import android.net.INetworkScoreService;
import android.net.NetworkScorerAppManager;
+import android.net.NetworkScorerAppManager.NetworkScorerAppData;
import android.net.ScoredNetwork;
import android.os.RemoteException;
import android.text.TextUtils;
@@ -165,12 +166,12 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
- String currentScorer = NetworkScorerAppManager.getActiveScorer(mContext);
+ NetworkScorerAppData currentScorer = NetworkScorerAppManager.getActiveScorer(mContext);
if (currentScorer == null) {
writer.println("Scoring is disabled.");
return;
}
- writer.println("Current scorer: " + currentScorer);
+ writer.println("Current scorer: " + currentScorer.mPackageName);
writer.flush();
for (INetworkScoreCache scoreCache : getScoreCaches()) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 570ac3d..c2523be 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1832,8 +1832,7 @@
final int N = mProcessCpuTracker.countStats();
for (int j=0; j<N; j++) {
ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(j);
- if (st.vsize <= 0 || st.uid >= Process.FIRST_APPLICATION_UID
- || st.uid == Process.SYSTEM_UID) {
+ if (st.vsize <= 0 || st.uid >= Process.FIRST_APPLICATION_UID) {
// This is definitely an application process; skip it.
continue;
}
@@ -1851,8 +1850,7 @@
if (DEBUG_PSS) Slog.d(TAG, "Collected native and kernel memory in "
+ (SystemClock.uptimeMillis()-start) + "ms");
mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(),
- memInfo.getFreeSizeKb(),
- memInfo.getSwapTotalSizeKb()-memInfo.getSwapFreeSizeKb(),
+ memInfo.getFreeSizeKb(), memInfo.getZramTotalSizeKb(),
memInfo.getBuffersSizeKb()+memInfo.getShmemSizeKb()
+memInfo.getSlabSizeKb(),
nativeTotalPss);
@@ -7238,6 +7236,7 @@
rti.stackId = tr.stack != null ? tr.stack.mStackId : -1;
rti.userId = tr.userId;
rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription);
+ rti.lastActiveTime = tr.lastActiveTime;
return rti;
}
@@ -7522,6 +7521,10 @@
Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode");
return;
}
+ final ActivityRecord prev = mStackSupervisor.topRunningActivityLocked();
+ if (prev != null && prev.isRecentsActivity()) {
+ task.setTaskToReturnTo(ActivityRecord.RECENTS_ACTIVITY_TYPE);
+ }
mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options);
} finally {
Binder.restoreCallingIdentity(origId);
@@ -7765,7 +7768,8 @@
// Since we lost lock on task, make sure it is still there.
task = mStackSupervisor.anyTaskForIdLocked(task.taskId);
if (task != null) {
- if ((mFocusedActivity == null) || (task != mFocusedActivity.task)) {
+ if (!isSystemInitiated
+ && ((mFocusedActivity == null) || (task != mFocusedActivity.task))) {
throw new IllegalArgumentException("Invalid task, not in foreground");
}
mStackSupervisor.setLockTaskModeLocked(task, isSystemInitiated);
@@ -12823,8 +12827,7 @@
if (nativeProcTotalPss > 0) {
synchronized (this) {
mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(),
- memInfo.getFreeSizeKb(),
- memInfo.getSwapTotalSizeKb()-memInfo.getSwapFreeSizeKb(),
+ memInfo.getFreeSizeKb(), memInfo.getZramTotalSizeKb(),
memInfo.getBuffersSizeKb()+memInfo.getShmemSizeKb()+memInfo.getSlabSizeKb(),
nativeProcTotalPss);
}
@@ -13491,7 +13494,7 @@
// activity manager to announce its creation.
public boolean bindBackupAgent(ApplicationInfo app, int backupMode) {
if (DEBUG_BACKUP) Slog.v(TAG, "bindBackupAgent: app=" + app + " mode=" + backupMode);
- enforceCallingPermission("android.permission.BACKUP", "bindBackupAgent");
+ enforceCallingPermission("android.permission.CONFIRM_FULL_BACKUP", "bindBackupAgent");
synchronized(this) {
// !!! TODO: currently no check here that we're already bound
@@ -13958,6 +13961,8 @@
// Make sure that the user who is receiving this broadcast is started.
// If not, we will just skip it.
+
+
if (userId != UserHandle.USER_ALL && mStartedUsers.get(userId) == null) {
if (callingUid != Process.SYSTEM_UID || (intent.getFlags()
& Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
@@ -13973,8 +13978,8 @@
*/
int callingAppId = UserHandle.getAppId(callingUid);
if (callingAppId == Process.SYSTEM_UID || callingAppId == Process.PHONE_UID
- || callingAppId == Process.SHELL_UID || callingAppId == Process.BLUETOOTH_UID
- || callingUid == 0) {
+ || callingAppId == Process.SHELL_UID || callingAppId == Process.BLUETOOTH_UID
+ || callingAppId == Process.NFC_UID || callingUid == 0) {
// Always okay.
} else if (callerApp == null || !callerApp.persistent) {
try {
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 0825f2e..f4e9876 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -79,8 +79,6 @@
private static final String ATTR_LAUNCHEDFROMPACKAGE = "launched_from_package";
private static final String ATTR_RESOLVEDTYPE = "resolved_type";
private static final String ATTR_COMPONENTSPECIFIED = "component_specified";
- private static final String ATTR_TASKDESCRIPTIONLABEL = "task_description_label";
- private static final String ATTR_TASKDESCRIPTIONCOLOR = "task_description_color";
private static final String ACTIVITY_ICON_SUFFIX = "_activity_icon_";
final ActivityManagerService service; // owner
@@ -1064,20 +1062,10 @@
}
out.attribute(null, ATTR_COMPONENTSPECIFIED, String.valueOf(componentSpecified));
out.attribute(null, ATTR_USERID, String.valueOf(userId));
+
if (taskDescription != null) {
- final String label = taskDescription.getLabel();
- if (label != null) {
- out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, label);
- }
- final int colorPrimary = taskDescription.getPrimaryColor();
- if (colorPrimary != 0) {
- out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR, Integer.toHexString(colorPrimary));
- }
- final Bitmap icon = taskDescription.getIcon();
- if (icon != null) {
- TaskPersister.saveImage(icon, String.valueOf(task.taskId) + ACTIVITY_ICON_SUFFIX +
- createTime);
- }
+ TaskPersister.saveTaskDescription(taskDescription, String.valueOf(task.taskId) +
+ ACTIVITY_ICON_SUFFIX + createTime, out);
}
out.startTag(null, TAG_INTENT);
@@ -1100,10 +1088,9 @@
String resolvedType = null;
boolean componentSpecified = false;
int userId = 0;
- String activityLabel = null;
- int activityColor = 0;
long createTime = -1;
final int outerDepth = in.getDepth();
+ TaskDescription taskDescription = new TaskDescription();
for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
final String attrName = in.getAttributeName(attrNdx);
@@ -1122,10 +1109,9 @@
componentSpecified = Boolean.valueOf(attrValue);
} else if (ATTR_USERID.equals(attrName)) {
userId = Integer.valueOf(attrValue);
- } else if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) {
- activityLabel = attrValue;
- } else if (ATTR_TASKDESCRIPTIONCOLOR.equals(attrName)) {
- activityColor = (int) Long.parseLong(attrValue, 16);
+ } else if (TaskPersister.readTaskDescriptionAttribute(taskDescription, attrName,
+ attrValue)) {
+ // Completed in TaskPersister.readTaskDescriptionAttribute()
} else {
Log.d(TAG, "Unknown ActivityRecord attribute=" + attrName);
}
@@ -1169,12 +1155,11 @@
r.persistentState = persistentState;
- Bitmap icon = null;
if (createTime >= 0) {
- icon = TaskPersister.restoreImage(String.valueOf(taskId) + ACTIVITY_ICON_SUFFIX +
- createTime);
+ taskDescription.setIcon(TaskPersister.restoreImage(String.valueOf(taskId) +
+ ACTIVITY_ICON_SUFFIX + createTime));
}
- r.taskDescription = new TaskDescription(activityLabel, icon, activityColor);
+ r.taskDescription = taskDescription;
r.createTime = createTime;
return r;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index aa777e1..e1c7da9 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1443,9 +1443,6 @@
mStackSupervisor.resumeHomeStackTask(returnTaskType, prev);
}
}
- if (prev != null && prev.isRecentsActivity()) {
- nextTask.setTaskToReturnTo(RECENTS_ACTIVITY_TYPE);
- }
// If we are sleeping, and there is no resumed activity, and the top
// activity is paused, well that is the state we want.
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 73266bc..e7f5720 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1981,6 +1981,9 @@
mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
intent, r.getUriPermissionsLocked(), r.userId);
+ if (sourceRecord != null && sourceRecord.isRecentsActivity()) {
+ r.task.setTaskToReturnTo(RECENTS_ACTIVITY_TYPE);
+ }
if (newTask) {
EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId);
}
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index c79b33d..132b244 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.app.ActivityManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Debug;
@@ -56,6 +57,9 @@
private static final String TAG_TASK = "task";
+ private static final String ATTR_TASKDESCRIPTIONLABEL = "task_description_label";
+ private static final String ATTR_TASKDESCRIPTIONCOLOR = "task_description_color";
+
private static File sImagesDir;
private static File sTasksDir;
@@ -143,6 +147,36 @@
}
}
+ static void saveTaskDescription(ActivityManager.TaskDescription taskDescription,
+ String iconFilename, XmlSerializer out) throws IOException {
+ if (taskDescription != null) {
+ final String label = taskDescription.getLabel();
+ if (label != null) {
+ out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, label);
+ }
+ final int colorPrimary = taskDescription.getPrimaryColor();
+ if (colorPrimary != 0) {
+ out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR, Integer.toHexString(colorPrimary));
+ }
+ final Bitmap icon = taskDescription.getIcon();
+ if (icon != null) {
+ saveImage(icon, iconFilename);
+ }
+ }
+ }
+
+ static boolean readTaskDescriptionAttribute(ActivityManager.TaskDescription taskDescription,
+ String attrName, String attrValue) {
+ if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) {
+ taskDescription.setLabel(attrValue);
+ return true;
+ } else if (ATTR_TASKDESCRIPTIONCOLOR.equals(attrName)) {
+ taskDescription.setPrimaryColor((int) Long.parseLong(attrValue, 16));
+ return true;
+ }
+ return false;
+ }
+
ArrayList<TaskRecord> restoreTasksLocked() {
final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index a301c4b..0bd7ef9 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -56,9 +56,11 @@
private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode";
private static final String ATTR_USERID = "user_id";
private static final String ATTR_TASKTYPE = "task_type";
+ private static final String ATTR_LASTACTIVETIME = "last_active_time";
private static final String ATTR_LASTDESCRIPTION = "last_description";
private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
+ private static final String LAST_ACTIVITY_ICON_SUFFIX = "_last_activity_icon_";
private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
@@ -136,8 +138,9 @@
TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, Intent _affinityIntent,
String _affinity, ComponentName _realActivity, ComponentName _origActivity,
boolean _rootWasReset, boolean _askedCompatMode, int _taskType, int _userId,
- String _lastDescription, ArrayList<ActivityRecord> activities, long lastTimeMoved,
- boolean neverRelinquishIdentity) {
+ String _lastDescription, ArrayList<ActivityRecord> activities, long _lastActiveTime,
+ long lastTimeMoved, boolean neverRelinquishIdentity,
+ ActivityManager.TaskDescription _lastTaskDescription) {
mService = service;
taskId = _taskId;
intent = _intent;
@@ -152,10 +155,12 @@
taskType = _taskType;
mTaskToReturnTo = HOME_ACTIVITY_TYPE;
userId = _userId;
+ lastActiveTime = _lastActiveTime;
lastDescription = _lastDescription;
mActivities = activities;
mLastTimeMoved = lastTimeMoved;
mNeverRelinquishIdentity = neverRelinquishIdentity;
+ lastTaskDescription = _lastTaskDescription;
}
void touchActiveTime() {
@@ -703,12 +708,18 @@
out.attribute(null, ATTR_ASKEDCOMPATMODE, String.valueOf(askedCompatMode));
out.attribute(null, ATTR_USERID, String.valueOf(userId));
out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType));
+ out.attribute(null, ATTR_LASTACTIVETIME, String.valueOf(lastActiveTime));
out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
out.attribute(null, ATTR_NEVERRELINQUISH, String.valueOf(mNeverRelinquishIdentity));
if (lastDescription != null) {
out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
}
+ if (lastTaskDescription != null) {
+ TaskPersister.saveTaskDescription(lastTaskDescription, String.valueOf(taskId) +
+ LAST_ACTIVITY_ICON_SUFFIX + lastActiveTime, out);
+ }
+
if (affinityIntent != null) {
out.startTag(null, TAG_AFFINITYINTENT);
affinityIntent.saveToXml(out);
@@ -753,10 +764,12 @@
int taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE;
int userId = 0;
String lastDescription = null;
+ long lastActiveTime = -1;
long lastTimeOnTop = 0;
boolean neverRelinquishIdentity = true;
int taskId = -1;
final int outerDepth = in.getDepth();
+ ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription();
for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
final String attrName = in.getAttributeName(attrNdx);
@@ -779,12 +792,17 @@
userId = Integer.valueOf(attrValue);
} else if (ATTR_TASKTYPE.equals(attrName)) {
taskType = Integer.valueOf(attrValue);
+ } else if (ATTR_LASTACTIVETIME.equals(attrName)) {
+ lastActiveTime = Long.valueOf(attrValue);
} else if (ATTR_LASTDESCRIPTION.equals(attrName)) {
lastDescription = attrValue;
} else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
lastTimeOnTop = Long.valueOf(attrValue);
} else if (ATTR_NEVERRELINQUISH.equals(attrName)) {
neverRelinquishIdentity = Boolean.valueOf(attrValue);
+ } else if (TaskPersister.readTaskDescriptionAttribute(taskDescription, attrName,
+ attrValue)) {
+ // Completed in TaskPersister.readTaskDescriptionAttribute()
} else {
Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
}
@@ -816,10 +834,15 @@
}
}
+ if (lastActiveTime >= 0) {
+ taskDescription.setIcon(TaskPersister.restoreImage(String.valueOf(taskId) +
+ LAST_ACTIVITY_ICON_SUFFIX + lastActiveTime));
+ }
+
final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
affinityIntent, affinity, realActivity, origActivity, rootHasReset,
- askedCompatMode, taskType, userId, lastDescription, activities, lastTimeOnTop,
- neverRelinquishIdentity);
+ askedCompatMode, taskType, userId, lastDescription, activities, lastActiveTime,
+ lastTimeOnTop, neverRelinquishIdentity, taskDescription);
for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 3b55bfc..a6485e9 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -172,11 +172,8 @@
throw new IllegalArgumentException("You must pass a valid uri and observer");
}
- final int callingUser = UserHandle.getCallingUserId();
- if (callingUser != userHandle) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "no permission to observe other users' provider view");
- }
+ enforceCrossUserPermission(userHandle,
+ "no permission to observe other users' provider view");
if (userHandle < 0) {
if (userHandle == UserHandle.USER_CURRENT) {
@@ -346,7 +343,15 @@
* @param request The request object. Validation of this object is done by its builder.
*/
public void sync(SyncRequest request) {
- int userId = UserHandle.getCallingUserId();
+ syncAsUser(request, UserHandle.getCallingUserId());
+ }
+
+ /**
+ * If the user id supplied is different to the calling user, the caller must hold the
+ * INTERACT_ACROSS_USERS_FULL permission.
+ */
+ public void syncAsUser(SyncRequest request, int userId) {
+ enforceCrossUserPermission(userId, "no permission to request sync as user: " + userId);
int callerUid = Binder.getCallingUid();
// This makes it so that future permission checks will be in the context of this
// process rather than the caller's process. We will restore this before returning.
@@ -399,11 +404,30 @@
*/
@Override
public void cancelSync(Account account, String authority, ComponentName cname) {
+ cancelSyncAsUser(account, authority, cname, UserHandle.getCallingUserId());
+ }
+
+ /**
+ * Clear all scheduled sync operations that match the uri and cancel the active sync
+ * if they match the authority and account, if they are present.
+ *
+ * <p> If the user id supplied is different to the calling user, the caller must hold the
+ * INTERACT_ACROSS_USERS_FULL permission.
+ *
+ * @param account filter the pending and active syncs to cancel using this account, or null.
+ * @param authority filter the pending and active syncs to cancel using this authority, or
+ * null.
+ * @param userId the user id for which to cancel sync operations.
+ * @param cname cancel syncs running on this service, or null for provider/account.
+ */
+ @Override
+ public void cancelSyncAsUser(Account account, String authority, ComponentName cname,
+ int userId) {
if (authority != null && authority.length() == 0) {
throw new IllegalArgumentException("Authority must be non-empty");
}
-
- int userId = UserHandle.getCallingUserId();
+ enforceCrossUserPermission(userId,
+ "no permission to modify the sync settings for user " + userId);
// This makes it so that future permission checks will be in the context of this
// process rather than the caller's process. We will restore this before returning.
long identityToken = clearCallingIdentity();
@@ -456,9 +480,23 @@
*/
@Override
public SyncAdapterType[] getSyncAdapterTypes() {
+ return getSyncAdapterTypesAsUser(UserHandle.getCallingUserId());
+ }
+
+ /**
+ * Get information about the SyncAdapters that are known to the system for a particular user.
+ *
+ * <p> If the user id supplied is different to the calling user, the caller must hold the
+ * INTERACT_ACROSS_USERS_FULL permission.
+ *
+ * @return an array of SyncAdapters that have registered with the system
+ */
+ @Override
+ public SyncAdapterType[] getSyncAdapterTypesAsUser(int userId) {
+ enforceCrossUserPermission(userId,
+ "no permission to read sync settings for user " + userId);
// This makes it so that future permission checks will be in the context of this
// process rather than the caller's process. We will restore this before returning.
- final int userId = UserHandle.getCallingUserId();
final long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
@@ -470,10 +508,20 @@
@Override
public boolean getSyncAutomatically(Account account, String providerName) {
+ return getSyncAutomaticallyAsUser(account, providerName, UserHandle.getCallingUserId());
+ }
+
+ /**
+ * If the user id supplied is different to the calling user, the caller must hold the
+ * INTERACT_ACROSS_USERS_FULL permission.
+ */
+ @Override
+ public boolean getSyncAutomaticallyAsUser(Account account, String providerName, int userId) {
+ enforceCrossUserPermission(userId,
+ "no permission to read the sync settings for user " + userId);
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
@@ -588,9 +636,18 @@
}
public int getIsSyncable(Account account, String providerName) {
+ return getIsSyncableAsUser(account, providerName, UserHandle.getCallingUserId());
+ }
+
+ /**
+ * If the user id supplied is different to the calling user, the caller must hold the
+ * INTERACT_ACROSS_USERS_FULL permission.
+ */
+ public int getIsSyncableAsUser(Account account, String providerName, int userId) {
+ enforceCrossUserPermission(userId,
+ "no permission to read the sync settings for user " + userId);
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -627,10 +684,20 @@
@Override
public boolean getMasterSyncAutomatically() {
+ return getMasterSyncAutomaticallyAsUser(UserHandle.getCallingUserId());
+ }
+
+ /**
+ * If the user id supplied is different to the calling user, the caller must hold the
+ * INTERACT_ACROSS_USERS_FULL permission.
+ */
+ @Override
+ public boolean getMasterSyncAutomaticallyAsUser(int userId) {
+ enforceCrossUserPermission(userId,
+ "no permission to read the sync settings for user " + userId);
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
SyncManager syncManager = getSyncManager();
@@ -679,10 +746,19 @@
}
public List<SyncInfo> getCurrentSyncs() {
+ return getCurrentSyncsAsUser(UserHandle.getCallingUserId());
+ }
+
+ /**
+ * If the user id supplied is different to the calling user, the caller must hold the
+ * INTERACT_ACROSS_USERS_FULL permission.
+ */
+ public List<SyncInfo> getCurrentSyncsAsUser(int userId) {
+ enforceCrossUserPermission(userId,
+ "no permission to read the sync settings for user " + userId);
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
"no permission to read the sync stats");
- int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
return getSyncManager().getSyncStorageEngine().getCurrentSyncsCopy(userId);
@@ -692,13 +768,24 @@
}
public SyncStatusInfo getSyncStatus(Account account, String authority, ComponentName cname) {
+ return getSyncStatusAsUser(account, authority, cname, UserHandle.getCallingUserId());
+ }
+
+ /**
+ * If the user id supplied is different to the calling user, the caller must hold the
+ * INTERACT_ACROSS_USERS_FULL permission.
+ */
+ public SyncStatusInfo getSyncStatusAsUser(Account account, String authority,
+ ComponentName cname, int userId) {
if (TextUtils.isEmpty(authority)) {
throw new IllegalArgumentException("Authority must not be empty");
}
+
+ enforceCrossUserPermission(userId,
+ "no permission to read the sync stats for user " + userId);
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
"no permission to read the sync stats");
- int userId = UserHandle.getCallingUserId();
int callerUid = Binder.getCallingUid();
long identityToken = clearCallingIdentity();
try {
@@ -772,6 +859,21 @@
}
/**
+ * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS_FULL
+ * permission, if the userHandle is not for the caller.
+ *
+ * @param userHandle the user handle of the user we want to act on behalf of.
+ * @param message the message to log on security exception.
+ */
+ private void enforceCrossUserPermission(int userHandle, String message) {
+ final int callingUser = UserHandle.getCallingUserId();
+ if (callingUser != userHandle) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+ }
+ }
+
+ /**
* Hide this class since it is not part of api,
* but current unittest framework requires it to be public
* @hide
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 8968da3..ed4ccfc 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -103,6 +103,9 @@
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+ if (Build.IS_DEBUGGABLE) {
+ SystemProperties.addChangeCallback(mSystemPropertiesChanged);
+ }
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -127,6 +130,7 @@
pw.println("mCurrentDreamCanDoze=" + mCurrentDreamCanDoze);
pw.println("mCurrentDreamIsDozing=" + mCurrentDreamIsDozing);
pw.println("mCurrentDreamDozeHardware=" + mCurrentDreamDozeHardware);
+ pw.println("getDozeComponent()=" + getDozeComponent());
pw.println();
DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() {
@@ -653,4 +657,18 @@
}
}
}
+
+ private final Runnable mSystemPropertiesChanged = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Slog.d(TAG, "System properties changed");
+ synchronized(mLock) {
+ if (mCurrentDreamName != null && mCurrentDreamCanDoze
+ && !mCurrentDreamName.equals(getDozeComponent())) {
+ // may have updated the doze component, wake up
+ stopDreamLocked();
+ }
+ }
+ }
+ };
}
diff --git a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
index 74eaf2a..8de6763 100644
--- a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
+++ b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
@@ -27,18 +27,18 @@
/**
* Handles CEC command <Active Source>.
* <p>
- * Used by feature actions that need to handle the command in their flow.
+ * Used by feature actions that need to handle the command in their flow. Only for TV
+ * local device.
*/
final class ActiveSourceHandler {
private static final String TAG = "ActiveSourceHandler";
- private final HdmiCecLocalDevice mSource;
+ private final HdmiCecLocalDeviceTv mSource;
private final HdmiControlService mService;
@Nullable
private final IHdmiControlCallback mCallback;
- static ActiveSourceHandler create(HdmiCecLocalDevice source,
- IHdmiControlCallback callback) {
+ static ActiveSourceHandler create(HdmiCecLocalDeviceTv source, IHdmiControlCallback callback) {
if (source == null) {
Slog.e(TAG, "Wrong arguments");
return null;
@@ -46,7 +46,7 @@
return new ActiveSourceHandler(source, callback);
}
- private ActiveSourceHandler(HdmiCecLocalDevice source, IHdmiControlCallback callback) {
+ private ActiveSourceHandler(HdmiCecLocalDeviceTv source, IHdmiControlCallback callback) {
mSource = source;
mService = mSource.getService();
mCallback = callback;
@@ -55,48 +55,46 @@
/**
* Handles the incoming active source command.
*
- * @param deviceLogicalAddress logical address of the device to be the active source
- * @param routingPath routing path of the device to be the active source
+ * @param activeAddress logical address of the device to be the active source
+ * @param activePath routing path of the device to be the active source
*/
- void process(int deviceLogicalAddress, int routingPath) {
- if (getSourcePath() == routingPath && mSource.getActiveSource() == getSourceAddress()) {
+ void process(int activeAddress, int activePath) {
+ // Seq #17
+ HdmiCecLocalDeviceTv tv = mSource;
+ if (getSourcePath() == activePath && tv.getActiveSource() == getSourceAddress()) {
invokeCallback(HdmiCec.RESULT_SUCCESS);
return;
}
- HdmiCecDeviceInfo device = mService.getDeviceInfo(deviceLogicalAddress);
+ HdmiCecDeviceInfo device = mService.getDeviceInfo(activeAddress);
if (device == null) {
// "New device action" initiated by <Active Source> does not require
// "Routing change action".
- mSource.addAndStartAction(new NewDeviceAction(mSource, deviceLogicalAddress,
- routingPath, false));
+ tv.addAndStartAction(new NewDeviceAction(tv, activeAddress, activePath, false));
}
- if (!mSource.isInPresetInstallationMode()) {
- int prevActiveInput = mSource.getActivePortId();
- mSource.updateActiveDevice(deviceLogicalAddress, routingPath);
- if (prevActiveInput != mSource.getActivePortId()) {
- // TODO: change port input here.
+ int currentActive = tv.getActiveSource();
+ int currentPath = tv.getActivePath();
+ if (!tv.isInPresetInstallationMode()) {
+ tv.updateActiveSource(activeAddress, activePath);
+ if (currentActive != activeAddress && currentPath != activePath) {
+ tv.updateActivePortId(mService.pathToPortId(activePath));
}
invokeCallback(HdmiCec.RESULT_SUCCESS);
} else {
// TV is in a mode that should keep its current source/input from
// being changed for its operation. Reclaim the active source
// or switch the port back to the one used for the current mode.
- if (mSource.getActiveSource() == getSourceAddress()) {
+ if (currentActive == getSourceAddress()) {
HdmiCecMessage activeSource =
- HdmiCecMessageBuilder.buildActiveSource(getSourceAddress(),
- getSourcePath());
+ HdmiCecMessageBuilder.buildActiveSource(currentActive, currentPath);
mService.sendCecCommand(activeSource);
- mSource.updateActiveDevice(deviceLogicalAddress, routingPath);
+ tv.updateActiveSource(currentActive, currentPath);
invokeCallback(HdmiCec.RESULT_SUCCESS);
} else {
- int activePath = mSource.getActivePath();
- mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(getSourceAddress(),
- routingPath, activePath));
- // TODO: Start port select action here
- // PortSelectAction action = new PortSelectAction(mService, getSourceAddress(),
- // activePath, mCallback);
- // mService.addActionAndStart(action);
+ HdmiCecMessage routingChange = HdmiCecMessageBuilder.buildRoutingChange(
+ getSourceAddress(), activePath, currentPath);
+ mService.sendCecCommand(routingChange);
+ tv.addAndStartAction(new RoutingControlAction(tv, currentPath, mCallback));
}
}
}
diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
index dbe3b80..b97350d 100644
--- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
@@ -77,7 +77,7 @@
* @param target target logical device that will be a new active source
* @param callback callback object
*/
- public DeviceSelectAction(HdmiCecLocalDevice source,
+ public DeviceSelectAction(HdmiCecLocalDeviceTv source,
HdmiCecDeviceInfo target, IHdmiControlCallback callback) {
super(source);
mCallback = callback;
@@ -116,7 +116,7 @@
if (opcode == HdmiCec.MESSAGE_ACTIVE_SOURCE && params.length == 2) {
int activePath = HdmiUtils.twoBytesToInt(params);
ActiveSourceHandler
- .create(localDevice(), mCallback)
+ .create((HdmiCecLocalDeviceTv) localDevice(), mCallback)
.process(cmd.getSource(), activePath);
finish();
return true;
@@ -164,8 +164,10 @@
}
private void turnOnDevice() {
- sendRemoteKeyCommand(HdmiConstants.UI_COMMAND_POWER);
- sendRemoteKeyCommand(HdmiConstants.UI_COMMAND_POWER_ON_FUNCTION);
+ sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
+ HdmiConstants.UI_COMMAND_POWER);
+ sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
+ HdmiConstants.UI_COMMAND_POWER_ON_FUNCTION);
mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
addTimer(mState, TIMEOUT_POWER_ON_MS);
}
@@ -177,13 +179,6 @@
addTimer(mState, TIMEOUT_ACTIVE_SOURCE_MS);
}
- private void sendRemoteKeyCommand(int keyCode) {
- sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(),
- mTarget.getLogicalAddress(), keyCode));
- sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(),
- mTarget.getLogicalAddress()));
- }
-
@Override
public void handleTimerEvent(int timeoutState) {
if (mState != timeoutState) {
diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java
index 0ec17f6..cf28f05 100644
--- a/services/core/java/com/android/server/hdmi/FeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/FeatureAction.java
@@ -248,4 +248,11 @@
protected final int getSourcePath() {
return mSource.getDeviceInfo().getPhysicalAddress();
}
+
+ protected void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) {
+ sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(
+ getSourceAddress(), targetAddress, uiCommand));
+ sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
+ getSourceAddress(), targetAddress));
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 6f7f5c2..bf7e57b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -125,6 +125,12 @@
return true;
}
switch (message.getOpcode()) {
+ case HdmiCec.MESSAGE_ACTIVE_SOURCE:
+ return handleActiveSource(message);
+ case HdmiCec.MESSAGE_INACTIVE_SOURCE:
+ return handleInactiveSource(message);
+ case HdmiCec.MESSAGE_REQUEST_ACTIVE_SOURCE:
+ return handleRequestActiveSource(message);
case HdmiCec.MESSAGE_GET_MENU_LANGUAGE:
return handleGetMenuLanguage(message);
case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS:
@@ -145,6 +151,8 @@
return handleSetSystemAudioMode(message);
case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
return handleSystemAudioModeStatus(message);
+ case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS:
+ return handleReportAudioStatus(message);
default:
return false;
}
@@ -193,6 +201,21 @@
}
@ServiceThreadOnly
+ protected boolean handleActiveSource(HdmiCecMessage message) {
+ return false;
+ }
+
+ @ServiceThreadOnly
+ protected boolean handleInactiveSource(HdmiCecMessage message) {
+ return false;
+ }
+
+ @ServiceThreadOnly
+ protected boolean handleRequestActiveSource(HdmiCecMessage message) {
+ return false;
+ }
+
+ @ServiceThreadOnly
protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
assertRunOnServiceThread();
Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString());
@@ -242,6 +265,10 @@
return false;
}
+ protected boolean handleReportAudioStatus(HdmiCecMessage message) {
+ return false;
+ }
+
@ServiceThreadOnly
final void handleAddressAllocated(int logicalAddress) {
assertRunOnServiceThread();
@@ -383,15 +410,24 @@
}
}
- /**
- * Returns the active routing path.
- */
+ void setActiveSource(int source) {
+ synchronized (mLock) {
+ mActiveSource = source;
+ }
+ }
+
int getActivePath() {
synchronized (mLock) {
return mActiveRoutingPath;
}
}
+ void setActivePath(int path) {
+ synchronized (mLock) {
+ mActiveRoutingPath = path;
+ }
+ }
+
/**
* Returns the ID of the active HDMI port. The active port is the one that has the active
* routing path connected to it directly or indirectly under the device hierarchy.
@@ -429,6 +465,13 @@
}
boolean isInPresetInstallationMode() {
+ // TODO: Change this to check the right flag.
+ synchronized (mLock) {
+ return !mInputChangeEnabled;
+ }
+ }
+
+ boolean isHdmiControlEnabled() {
synchronized (mLock) {
return !mInputChangeEnabled;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 2b53895..718072a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -20,6 +20,7 @@
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
import android.hardware.hdmi.IHdmiControlCallback;
+import android.media.AudioSystem;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
@@ -47,6 +48,19 @@
@GuardedBy("mLock")
private boolean mSystemAudioMode;
+ // The previous port id (input) before switching to the new one. This is remembered in order to
+ // be able to switch to it upon receiving <Inactive Source> from currently active source.
+ // This remains valid only when the active source was switched via one touch play operation
+ // (either by TV or source device). Manual port switching invalidates this value to
+ // HdmiConstants.PORT_INVALID, for which case <Inactive Source> does not do anything.
+ @GuardedBy("mLock")
+ private int mPrevPortId;
+
+ @GuardedBy("mLock")
+ private int mSystemAudioVolume = HdmiConstants.UNKNOWN_VOLUME;
+
+ @GuardedBy("mLock")
+ private boolean mSystemAudioMute = false;
// Copy of mDeviceInfos to guarantee thread-safety.
@GuardedBy("mLock")
@@ -62,7 +76,7 @@
HdmiCecLocalDeviceTv(HdmiControlService service) {
super(service, HdmiCec.DEVICE_TV);
-
+ mPrevPortId = HdmiConstants.INVALID_PORT_ID;
// TODO: load system audio mode and set it to mSystemAudioMode.
}
@@ -90,6 +104,10 @@
@ServiceThreadOnly
void deviceSelect(int targetAddress, IHdmiControlCallback callback) {
assertRunOnServiceThread();
+ if (targetAddress == HdmiCec.ADDR_INTERNAL) {
+ handleSelectInternalSource(callback);
+ return;
+ }
HdmiCecDeviceInfo targetDevice = getDeviceInfo(targetAddress);
if (targetDevice == null) {
invokeCallback(callback, HdmiCec.RESULT_TARGET_NOT_AVAILABLE);
@@ -99,30 +117,85 @@
addAndStartAction(new DeviceSelectAction(this, targetDevice, callback));
}
- /**
- * Performs the action routing control.
- *
- * @param portId new HDMI port to route to
- * @param callback callback object to report the result with
- */
@ServiceThreadOnly
- void portSelect(int portId, IHdmiControlCallback callback) {
+ private void handleSelectInternalSource(IHdmiControlCallback callback) {
assertRunOnServiceThread();
- if (isInPresetInstallationMode()) {
+ // Seq #18
+ if (isHdmiControlEnabled() && getActiveSource() != mAddress) {
+ updateActiveSource(mAddress, mService.getPhysicalAddress());
+ // TODO: Check if this comes from <Text/Image View On> - if true, do nothing.
+ HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
+ mAddress, mService.getPhysicalAddress());
+ mService.sendCecCommand(activeSource);
+ }
+ }
+
+ @ServiceThreadOnly
+ void updateActiveSource(int activeSource, int activePath) {
+ assertRunOnServiceThread();
+ // Seq #14
+ if (activeSource == getActiveSource() && activePath == getActivePath()) {
+ return;
+ }
+ setActiveSource(activeSource);
+ setActivePath(activePath);
+ if (getDeviceInfo(activeSource) != null && activeSource != mAddress) {
+ if (mService.pathToPortId(activePath) == getActivePortId()) {
+ setPrevPortId(getActivePortId());
+ }
+ // TODO: Show the OSD banner related to the new active source device.
+ } else {
+ // TODO: If displayed, remove the OSD banner related to the previous
+ // active source device.
+ }
+ }
+
+ /**
+ * Returns the previous port id kept to handle input switching on <Inactive Source>.
+ */
+ int getPrevPortId() {
+ synchronized (mLock) {
+ return mPrevPortId;
+ }
+ }
+
+ /**
+ * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be
+ * taken for <Inactive Source>.
+ */
+ void setPrevPortId(int portId) {
+ synchronized (mLock) {
+ mPrevPortId = portId;
+ }
+ }
+
+ @ServiceThreadOnly
+ void updateActivePortId(int portId) {
+ assertRunOnServiceThread();
+ // Seq #15
+ if (portId == getActivePortId()) {
+ return;
+ }
+ setPrevPortId(portId);
+ // TODO: Actually switch the physical port here. Handle PAP/PIP as well.
+ // Show OSD port change banner
+ }
+
+ @ServiceThreadOnly
+ void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
+ assertRunOnServiceThread();
+ // Seq #20
+ if (!isHdmiControlEnabled() || portId == getActivePortId()) {
invokeCallback(callback, HdmiCec.RESULT_INCORRECT_MODE);
return;
}
- // Make sure this call does not stem from <Active Source> message reception, in
- // which case the two ports will be the same.
- if (portId == getActivePortId()) {
- invokeCallback(callback, HdmiCec.RESULT_SUCCESS);
- return;
- }
- setActivePortId(portId);
+ // TODO: Make sure this call does not stem from <Active Source> message reception.
+ setActivePortId(portId);
// TODO: Return immediately if the operation is triggered by <Text/Image View On>
+ // and this is the first notification about the active input after power-on.
// TODO: Handle invalid port id / active input which should be treated as an
- // internal tuner.
+ // internal tuner.
removeAction(RoutingControlAction.class);
@@ -168,6 +241,61 @@
@Override
@ServiceThreadOnly
+ protected boolean handleActiveSource(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int activePath = HdmiUtils.twoBytesToInt(message.getParams());
+ ActiveSourceHandler.create(this, null).process(message.getSource(), activePath);
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleInactiveSource(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // Seq #10
+
+ // Ignore <Inactive Source> from non-active source device.
+ if (getActiveSource() != message.getSource()) {
+ return true;
+ }
+ if (isInPresetInstallationMode()) {
+ return true;
+ }
+ int portId = getPrevPortId();
+ if (portId != HdmiConstants.INVALID_PORT_ID) {
+ // TODO: Do this only if TV is not showing multiview like PIP/PAP.
+
+ HdmiCecDeviceInfo inactiveSource = getDeviceInfo(message.getSource());
+ if (inactiveSource == null) {
+ return true;
+ }
+ if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) {
+ return true;
+ }
+ // TODO: Switch the TV freeze mode off
+
+ setActivePortId(portId);
+ doManualPortSwitching(portId, null);
+ setPrevPortId(HdmiConstants.INVALID_PORT_ID);
+ }
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleRequestActiveSource(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // Seq #19
+ int address = getDeviceInfo().getLogicalAddress();
+ if (address == getActiveSource()) {
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildActiveSource(address, getActivePath()));
+ }
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
protected boolean handleGetMenuLanguage(HdmiCecMessage message) {
assertRunOnServiceThread();
HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand(
@@ -231,6 +359,22 @@
return true;
}
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleReportAudioStatus(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+
+ byte params[] = message.getParams();
+ if (params.length < 1) {
+ Slog.w(TAG, "Invalide <Report Audio Status> message:" + message);
+ return true;
+ }
+ int mute = params[0] & 0x80;
+ int volume = params[0] & 0x7F;
+ setAudioStatus(mute == 0x80, volume);
+ return true;
+ }
+
@ServiceThreadOnly
private void launchDeviceDiscovery() {
assertRunOnServiceThread();
@@ -312,15 +456,21 @@
boolean oldStatus = mArcStatusEnabled;
// 1. Enable/disable ARC circuit.
mService.setAudioReturnChannel(enabled);
-
- // TODO: notify arc mode change to AudioManager.
-
- // 2. Update arc status;
+ // 2. Notify arc status to audio service.
+ notifyArcStatusToAudioService(enabled);
+ // 3. Update arc status;
mArcStatusEnabled = enabled;
return oldStatus;
}
}
+ private void notifyArcStatusToAudioService(boolean enabled) {
+ // Note that we don't set any name to ARC.
+ mService.getAudioManager().setWiredDeviceConnectionState(
+ AudioSystem.DEVICE_OUT_HDMI_ARC,
+ enabled ? 1 : 0, "");
+ }
+
/**
* Returns whether ARC is enabled or not.
*/
@@ -330,9 +480,64 @@
}
}
- @ServiceThreadOnly
void setAudioStatus(boolean mute, int volume) {
- mService.setAudioStatus(mute, volume);
+ synchronized (mLock) {
+ mSystemAudioMute = mute;
+ mSystemAudioVolume = volume;
+ // TODO: pass volume to service (audio service) after scale it to local volume level.
+ mService.setAudioStatus(mute, volume);
+ }
+ }
+
+ @ServiceThreadOnly
+ void changeVolume(int curVolume, int delta, int maxVolume) {
+ assertRunOnServiceThread();
+ if (delta == 0 || !isSystemAudioOn()) {
+ return;
+ }
+
+ int targetVolume = curVolume + delta;
+ int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume);
+ synchronized (mLock) {
+ // If new volume is the same as current system audio volume, just ignore it.
+ // Note that UNKNOWN_VOLUME is not in range of cec volume scale.
+ if (cecVolume == mSystemAudioVolume) {
+ // Update tv volume with system volume value.
+ mService.setAudioStatus(false,
+ VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume));
+ return;
+ }
+ }
+
+ // Remove existing volume action.
+ removeAction(VolumeControlAction.class);
+
+ HdmiCecDeviceInfo avr = getAvrDeviceInfo();
+ addAndStartAction(VolumeControlAction.ofVolumeChange(this, avr.getLogicalAddress(),
+ cecVolume, delta > 0));
+ }
+
+ @ServiceThreadOnly
+ void changeMute(boolean mute) {
+ assertRunOnServiceThread();
+ if (!isSystemAudioOn()) {
+ return;
+ }
+
+ // Remove existing volume action.
+ removeAction(VolumeControlAction.class);
+ HdmiCecDeviceInfo avr = getAvrDeviceInfo();
+ addAndStartAction(VolumeControlAction.ofMute(this, avr.getLogicalAddress(), mute));
+ }
+
+ private boolean isSystemAudioOn() {
+ if (getAvrDeviceInfo() == null) {
+ return false;
+ }
+
+ synchronized (mLock) {
+ return mSystemAudioMode;
+ }
}
@Override
@@ -613,7 +818,6 @@
@ServiceThreadOnly
void onHotplug(int portNo, boolean connected) {
assertRunOnServiceThread();
- // TODO: delegate onHotplug event to each local device.
// Tv device will have permanent HotplugDetectionAction.
List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class);
@@ -623,10 +827,4 @@
hotplugActions.get(0).pollAllDevicesNow();
}
}
-
- boolean canChangeSystemAudio() {
- // TODO: implement this.
- // return true if no system audio control sequence is running.
- return false;
- }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiConstants.java b/services/core/java/com/android/server/hdmi/HdmiConstants.java
index 5294506..ab5b8d8 100644
--- a/services/core/java/com/android/server/hdmi/HdmiConstants.java
+++ b/services/core/java/com/android/server/hdmi/HdmiConstants.java
@@ -97,5 +97,12 @@
static final int UNKNOWN_VOLUME = -1;
+ // IRT(Initiator Repetition Time) in millisecond as recommended in the standard.
+ // Outgoing UCP commands, when in 'Press and Hold' mode, should be this much apart
+ // from the adjacent one so as not to place unnecessarily heavy load on the CEC line.
+ // TODO: This value might need tweaking per product basis. Consider putting it
+ // in config.xml to allow customization.
+ static final int IRT_MS = 300;
+
private HdmiConstants() { /* cannot be instantiated */ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 1a18c10..ad95181 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -28,6 +28,7 @@
import android.hardware.hdmi.IHdmiDeviceEventListener;
import android.hardware.hdmi.IHdmiHotplugEventListener;
import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
+import android.media.AudioManager;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
@@ -581,7 +582,7 @@
invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
return;
}
- tv.portSelect(portId, callback);
+ tv.doManualPortSwitching(portId, callback);
}
});
}
@@ -870,4 +871,8 @@
private HdmiCecLocalDevicePlayback playback() {
return (HdmiCecLocalDevicePlayback) mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK);
}
+
+ AudioManager getAudioManager() {
+ return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java
index c3078a2..5d81251 100644
--- a/services/core/java/com/android/server/hdmi/SendKeyAction.java
+++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java
@@ -15,6 +15,8 @@
*/
package com.android.server.hdmi;
+import static com.android.server.hdmi.HdmiConstants.IRT_MS;
+
import android.hardware.hdmi.HdmiCecMessage;
import android.util.Slog;
import android.view.KeyEvent;
@@ -38,13 +40,6 @@
// persists throughout the process till it is set back to {@code STATE_NONE} at the end.
private static final int STATE_PROCESSING_KEYCODE = 1;
- // IRT(Initiator Repetition Time) in millisecond as recommended in the standard.
- // Outgoing UCP commands, when in 'Press and Hold' mode, should be this much apart
- // from the adjacent one so as not to place unnecessarily heavy load on the CEC line.
- // TODO: This value might need tweaking per product basis. Consider putting it
- // in config.xml to allow customization.
- private static final int IRT_MS = 450;
-
// Logical address of the device to which the UCP/UCP commands are sent.
private final int mTargetAddress;
@@ -77,7 +72,6 @@
*
* @param keyCode key code of {@link KeyEvent} object
* @param isPressed true if the key event is of {@link KeyEvent#ACTION_DOWN}
- * @param param additional parameter that comes with the key event
*/
void processKeyEvent(int keyCode, boolean isPressed) {
if (mState != STATE_PROCESSING_KEYCODE) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
index 845eaa9..6f4164b 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
@@ -115,6 +115,7 @@
}
private boolean canChangeSystemAudio() {
- return tv().canChangeSystemAudio();
+ return !(tv().hasAction(SystemAudioActionFromTv.class)
+ || tv().hasAction(SystemAudioActionFromAvr.class));
}
}
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
index 89206a7..ecb158b 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
@@ -72,19 +72,12 @@
int uiCommand = tv().getSystemAudioMode()
? HdmiConstants.UI_COMMAND_RESTORE_VOLUME_FUNCTION // SystemAudioMode: ON
: HdmiConstants.UI_COMMAND_MUTE_FUNCTION; // SystemAudioMode: OFF
- sendUserControlPressedAndReleased(uiCommand);
+ sendUserControlPressedAndReleased(mAvrAddress, uiCommand);
// Still return SUCCESS to callback.
finishWithCallback(HdmiCec.RESULT_SUCCESS);
}
- private void sendUserControlPressedAndReleased(int uiCommand) {
- sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(
- getSourceAddress(), mAvrAddress, uiCommand));
- sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
- getSourceAddress(), mAvrAddress));
- }
-
@Override
boolean processCommand(HdmiCecMessage cmd) {
if (mState != STATE_WAIT_FOR_REPORT_AUDIO_STATUS) {
@@ -109,7 +102,7 @@
if ((tv().getSystemAudioMode() && mute) || (!tv().getSystemAudioMode() && !mute)) {
// Toggle AVR's mute status to match with the system audio status.
- sendUserControlPressedAndReleased(HdmiConstants.UI_COMMAND_MUTE);
+ sendUserControlPressedAndReleased(mAvrAddress, HdmiConstants.UI_COMMAND_MUTE);
}
finishWithCallback(HdmiCec.RESULT_SUCCESS);
} else {
diff --git a/services/core/java/com/android/server/hdmi/VolumeControlAction.java b/services/core/java/com/android/server/hdmi/VolumeControlAction.java
new file mode 100644
index 0000000..07c72f7
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/VolumeControlAction.java
@@ -0,0 +1,222 @@
+/*
+ * 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.hdmi;
+
+import static com.android.server.hdmi.HdmiConstants.IRT_MS;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Feature action that transmits volume change to Audio Receiver.
+ * <p>
+ * This action is created when a user pressed volume up/down. However, Since Android only provides a
+ * listener for delta of some volume change, we will set a target volume, and check reported volume
+ * from Audio Receiver(AVR). If TV receives no <Report Audio Status> from AVR, this action
+ * will be finished in {@link #IRT_MS} * {@link #VOLUME_CHANGE_TIMEOUT_MAX_COUNT} (ms).
+ */
+final class VolumeControlAction extends FeatureAction {
+ private static final String TAG = "VolumeControlAction";
+
+ private static final int VOLUME_MUTE = 101;
+ private static final int VOLUME_RESTORE = 102;
+ private static final int MAX_VOLUME = 100;
+ private static final int MIN_VOLUME = 0;
+
+ // State where to wait for <Report Audio Status>
+ private static final int STATE_WAIT_FOR_REPORT_VOLUME_STATUS = 1;
+
+ // Maximum count of time out used to finish volume action.
+ private static final int VOLUME_CHANGE_TIMEOUT_MAX_COUNT = 2;
+
+ private final int mAvrAddress;
+ private final int mTargetVolume;
+ private final boolean mIsVolumeUp;
+ private int mTimeoutCount;
+
+ /**
+ * Create a {@link VolumeControlAction} for mute/restore change
+ *
+ * @param source source device sending volume change
+ * @param avrAddress address of audio receiver
+ * @param mute whether to mute sound or not. {@code true} for mute on; {@code false} for mute
+ * off, i.e restore volume
+ * @return newly created {@link VolumeControlAction}
+ */
+ public static VolumeControlAction ofMute(HdmiCecLocalDevice source, int avrAddress,
+ boolean mute) {
+ return new VolumeControlAction(source, avrAddress, mute ? VOLUME_MUTE : VOLUME_RESTORE,
+ false);
+ }
+
+ /**
+ * Create a {@link VolumeControlAction} for volume up/down change
+ *
+ * @param source source device sending volume change
+ * @param avrAddress address of audio receiver
+ * @param targetVolume target volume to be set to AVR. It should be in range of [0-100]
+ * @param isVolumeUp whether to volume up or not. {@code true} for volume up; {@code false} for
+ * volume down
+ * @return newly created {@link VolumeControlAction}
+ */
+ public static VolumeControlAction ofVolumeChange(HdmiCecLocalDevice source, int avrAddress,
+ int targetVolume, boolean isVolumeUp) {
+ Preconditions.checkArgumentInRange(targetVolume, MIN_VOLUME, MAX_VOLUME, "volume");
+ return new VolumeControlAction(source, avrAddress, targetVolume, isVolumeUp);
+ }
+
+ /**
+ * Scale a custom volume value to cec volume scale.
+ *
+ * @param volume volume value in custom scale
+ * @param scale scale of volume (max volume)
+ * @return a volume scaled to cec volume range
+ */
+ public static int scaleToCecVolume(int volume, int scale) {
+ return (volume * MAX_VOLUME) / scale;
+ }
+
+ /**
+ * Scale a cec volume which is in range of 0 to 100 to custom volume level.
+ *
+ * @param cecVolume volume value in cec volume scale. It should be in a range of [0-100]
+ * @param scale scale of custom volume (max volume)
+ * @return a volume value scaled to custom volume range
+ */
+ public static int scaleToCustomVolume(int cecVolume, int scale) {
+ return (cecVolume * scale) / MAX_VOLUME;
+ }
+
+ private VolumeControlAction(HdmiCecLocalDevice source, int avrAddress, int targetVolume,
+ boolean isVolumeUp) {
+ super(source);
+
+ mAvrAddress = avrAddress;
+ mTargetVolume = targetVolume;
+ mIsVolumeUp = isVolumeUp;
+ }
+
+ @Override
+ boolean start() {
+ if (isForMute()) {
+ sendMuteChange(mTargetVolume == VOLUME_MUTE);
+ finish();
+ return true;
+ }
+
+ startVolumeChange();
+ return true;
+ }
+
+
+ private boolean isForMute() {
+ return mTargetVolume == VOLUME_MUTE || mTargetVolume == VOLUME_RESTORE;
+ }
+
+
+ private void startVolumeChange() {
+ mTimeoutCount = 0;
+ sendVolumeChange(mIsVolumeUp);
+ mState = STATE_WAIT_FOR_REPORT_VOLUME_STATUS;
+ addTimer(mState, IRT_MS);
+ }
+
+ private void sendVolumeChange(boolean up) {
+ sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(getSourceAddress(), mAvrAddress,
+ up ? HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP
+ : HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN));
+ }
+
+ private void sendMuteChange(boolean mute) {
+ sendUserControlPressedAndReleased(mAvrAddress,
+ mute ? HdmiConstants.UI_COMMAND_MUTE_FUNCTION :
+ HdmiConstants.UI_COMMAND_RESTORE_VOLUME_FUNCTION);
+ }
+
+ @Override
+ boolean processCommand(HdmiCecMessage cmd) {
+ if (mState != STATE_WAIT_FOR_REPORT_VOLUME_STATUS) {
+ return false;
+ }
+
+ switch (cmd.getOpcode()) {
+ case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS:
+ handleReportAudioStatus(cmd);
+ return true;
+ case HdmiCec.MESSAGE_FEATURE_ABORT:
+ // TODO: handle feature abort.
+ finish();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private void handleReportAudioStatus(HdmiCecMessage cmd) {
+ byte[] params = cmd.getParams();
+ if (params.length != 1) {
+ Slog.e(TAG, "Invalid <Report Audio Status> message:" + cmd);
+ return;
+ }
+
+ int volume = params[0] & 0x7F;
+ // Update volume with new value.
+ // Note that it will affect system volume change.
+ tv().setAudioStatus(false, volume);
+ if (mIsVolumeUp) {
+ if (mTargetVolume <= volume) {
+ finishWithVolumeChangeRelease();
+ return;
+ }
+ } else {
+ if (mTargetVolume >= volume) {
+ finishWithVolumeChangeRelease();
+ return;
+ }
+ }
+
+ // Clear action status and send another volume change command.
+ clear();
+ startVolumeChange();
+ }
+
+ private void finishWithVolumeChangeRelease() {
+ sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
+ getSourceAddress(), mAvrAddress));
+ finish();
+ }
+
+ @Override
+ void handleTimerEvent(int state) {
+ if (mState != STATE_WAIT_FOR_REPORT_VOLUME_STATUS) {
+ return;
+ }
+
+ // If no report volume action after IRT * VOLUME_CHANGE_TIMEOUT_MAX_COUNT just stop volume
+ // action.
+ if (++mTimeoutCount == VOLUME_CHANGE_TIMEOUT_MAX_COUNT) {
+ finishWithVolumeChangeRelease();
+ return;
+ }
+
+ sendVolumeChange(mIsVolumeUp);
+ addTimer(mState, IRT_MS);
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 1264741..aeade50 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -16,11 +16,9 @@
package com.android.server.media;
-import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.media.routeprovider.RouteRequest;
import android.media.session.ISessionController;
import android.media.session.ISessionControllerCallback;
@@ -40,6 +38,7 @@
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.VolumeProvider;
+import android.os.Binder;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
@@ -49,7 +48,6 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -87,6 +85,12 @@
*/
private static final int ACTIVE_BUFFER = 30000;
+ /**
+ * The amount of time we'll send an assumed volume after the last volume
+ * command before reverting to the last reported volume.
+ */
+ private static final int OPTIMISTIC_VOLUME_TIMEOUT = 1000;
+
private final MessageHandler mHandler;
private final int mOwnerPid;
@@ -122,11 +126,12 @@
// Volume handling fields
private AudioManager mAudioManager;
- private int mVolumeType = MediaSession.VOLUME_TYPE_LOCAL;
+ private int mVolumeType = MediaSession.PLAYBACK_TYPE_LOCAL;
private int mAudioStream = AudioManager.STREAM_MUSIC;
private int mVolumeControlType = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
private int mMaxVolume = 0;
private int mCurrentVolume = 0;
+ private int mOptimisticVolume = -1;
// End volume handling fields
private boolean mIsActive = false;
@@ -276,7 +281,10 @@
* @param delta The amount to adjust the volume by.
*/
public void adjustVolumeBy(int delta, int flags) {
- if (mVolumeType == MediaSession.VOLUME_TYPE_LOCAL) {
+ if (isPlaybackActive(false)) {
+ flags &= ~AudioManager.FLAG_PLAY_SOUND;
+ }
+ if (mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL) {
if (delta == 0) {
mAudioManager.adjustStreamVolume(mAudioStream, delta, flags);
} else {
@@ -298,18 +306,46 @@
return;
}
mSessionCb.adjustVolumeBy(delta);
+
+ int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
+ mOptimisticVolume = volumeBefore + delta;
+ mOptimisticVolume = Math.max(0, Math.min(mOptimisticVolume, mMaxVolume));
+ mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
+ mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
+ if (volumeBefore != mOptimisticVolume) {
+ pushVolumeUpdate();
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Adjusted optimistic volume to " + mOptimisticVolume + " max is "
+ + mMaxVolume);
+ }
}
}
public void setVolumeTo(int value, int flags) {
- if (mVolumeType == MediaSession.VOLUME_TYPE_LOCAL) {
+ if (mVolumeType == MediaSession.PLAYBACK_TYPE_LOCAL) {
mAudioManager.setStreamVolume(mAudioStream, value, flags);
} else {
if (mVolumeControlType != VolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
// Nothing to do. The volume can't be set directly.
return;
}
+ value = Math.max(0, Math.min(value, mMaxVolume));
mSessionCb.setVolumeTo(value);
+
+ int volumeBefore = (mOptimisticVolume < 0 ? mCurrentVolume : mOptimisticVolume);
+ mOptimisticVolume = Math.max(0, Math.min(value, mMaxVolume));
+ mHandler.removeCallbacks(mClearOptimisticVolumeRunnable);
+ mHandler.postDelayed(mClearOptimisticVolumeRunnable, OPTIMISTIC_VOLUME_TIMEOUT);
+ if (volumeBefore != mOptimisticVolume) {
+ pushVolumeUpdate();
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Set optimistic volume to " + mOptimisticVolume + " max is "
+ + mMaxVolume);
+ }
}
}
@@ -427,6 +463,16 @@
}
/**
+ * Get the volume we'd like it to be set to. This is only valid for a short
+ * while after a call to adjust or set volume.
+ *
+ * @return The current optimistic volume or -1.
+ */
+ public int getOptimisticVolume() {
+ return mOptimisticVolume;
+ }
+
+ /**
* @return True if this session is currently connected to a route.
*/
public boolean isConnected() {
@@ -542,8 +588,7 @@
cb.onPlaybackStateChanged(mPlaybackState);
} catch (DeadObjectException e) {
mControllerCallbacks.remove(i);
- Log.w(TAG, "Removed dead callback in pushPlaybackStateUpdate. size="
- + mControllerCallbacks.size() + " cb=" + cb, e);
+ Log.w(TAG, "Removed dead callback in pushPlaybackStateUpdate.", e);
} catch (RemoteException e) {
Log.w(TAG, "unexpected exception in pushPlaybackStateUpdate.", e);
}
@@ -561,10 +606,29 @@
try {
cb.onMetadataChanged(mMetadata);
} catch (DeadObjectException e) {
- Log.w(TAG, "Removing dead callback in pushMetadataUpdate. " + cb, e);
+ Log.w(TAG, "Removing dead callback in pushMetadataUpdate. ", e);
mControllerCallbacks.remove(i);
} catch (RemoteException e) {
- Log.w(TAG, "unexpected exception in pushMetadataUpdate. " + cb, e);
+ Log.w(TAG, "unexpected exception in pushMetadataUpdate. ", e);
+ }
+ }
+ }
+ }
+
+ private void pushVolumeUpdate() {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ return;
+ }
+ ParcelableVolumeInfo info = mController.getVolumeAttributes();
+ for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
+ ISessionControllerCallback cb = mControllerCallbacks.get(i);
+ try {
+ cb.onVolumeInfoChanged(info);
+ } catch (DeadObjectException e) {
+ Log.w(TAG, "Removing dead callback in pushVolumeUpdate. ", e);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unexpected exception in pushVolumeUpdate. ", e);
}
}
}
@@ -680,6 +744,17 @@
}
};
+ private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
+ @Override
+ public void run() {
+ boolean needUpdate = (mOptimisticVolume != mCurrentVolume);
+ mOptimisticVolume = -1;
+ if (needUpdate) {
+ pushVolumeUpdate();
+ }
+ }
+ };
+
private final class SessionStub extends ISession.Stub {
@Override
public void destroy() {
@@ -785,12 +860,14 @@
@Override
public void setCurrentVolume(int volume) {
mCurrentVolume = volume;
+ mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
}
@Override
public void configureVolumeHandling(int type, int arg1, int arg2) throws RemoteException {
+ boolean typeChanged = type != mVolumeType;
switch(type) {
- case MediaSession.VOLUME_TYPE_LOCAL:
+ case MediaSession.PLAYBACK_TYPE_LOCAL:
mVolumeType = type;
int audioStream = arg1;
if (isValidStream(audioStream)) {
@@ -800,7 +877,7 @@
mAudioStream = AudioManager.STREAM_MUSIC;
}
break;
- case MediaSession.VOLUME_TYPE_REMOTE:
+ case MediaSession.PLAYBACK_TYPE_REMOTE:
mVolumeType = type;
mVolumeControlType = arg1;
mMaxVolume = arg2;
@@ -809,6 +886,9 @@
throw new IllegalArgumentException("Volume handling type " + type
+ " not recognized.");
}
+ if (typeChanged) {
+ mService.onSessionPlaybackTypeChanged(MediaSessionRecord.this);
+ }
}
private boolean isValidStream(int stream) {
@@ -1027,10 +1107,11 @@
int type;
int max;
int current;
- if (mVolumeType == MediaSession.VOLUME_TYPE_REMOTE) {
+ if (mVolumeType == MediaSession.PLAYBACK_TYPE_REMOTE) {
type = mVolumeControlType;
max = mMaxVolume;
- current = mCurrentVolume;
+ current = mOptimisticVolume != -1 ? mOptimisticVolume
+ : mCurrentVolume;
} else {
type = VolumeProvider.VOLUME_CONTROL_ABSOLUTE;
max = mAudioManager.getStreamMaxVolume(mAudioStream);
@@ -1042,12 +1123,22 @@
@Override
public void adjustVolumeBy(int delta, int flags) {
- MediaSessionRecord.this.adjustVolumeBy(delta, flags);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ MediaSessionRecord.this.adjustVolumeBy(delta, flags);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
public void setVolumeTo(int value, int flags) {
- MediaSessionRecord.this.setVolumeTo(value, flags);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ MediaSessionRecord.this.setVolumeTo(value, flags);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
@@ -1130,6 +1221,7 @@
private static final int MSG_UPDATE_ROUTE_FILTERS = 5;
private static final int MSG_SEND_COMMAND = 6;
private static final int MSG_UPDATE_SESSION_STATE = 7;
+ private static final int MSG_UPDATE_VOLUME = 8;
public MessageHandler(Looper looper) {
super(looper);
@@ -1157,6 +1249,9 @@
case MSG_UPDATE_SESSION_STATE:
// TODO add session state
break;
+ case MSG_UPDATE_VOLUME:
+ pushVolumeUpdate();
+ break;
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index c0b7d68..fe68a86 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -27,8 +27,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.media.AudioManager;
import android.media.IAudioService;
+import android.media.IRemoteVolumeController;
import android.media.routeprovider.RouteRequest;
import android.media.session.IActiveSessionsListener;
import android.media.session.ISession;
@@ -98,8 +98,9 @@
// session so we drop late callbacks properly.
private int mShowRoutesRequestId = 0;
- // TODO refactor to have per user state for providers. See
- // MediaRouterService for an example
+ // Used to notify system UI when remote volume was changed. TODO find a
+ // better way to handle this.
+ private IRemoteVolumeController mRvc;
public MediaSessionService(Context context) {
super(context);
@@ -225,6 +226,16 @@
}
}
+ public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
+ synchronized (mLock) {
+ if (!mAllSessions.contains(record)) {
+ Log.d(TAG, "Unknown session changed playback type. Ignoring.");
+ return;
+ }
+ pushRemoteVolumeUpdateLocked(record.getUserId());
+ }
+ }
+
@Override
public void onStartUser(int userHandle) {
updateUser();
@@ -367,6 +378,13 @@
}
}
+ private void enforceStatusBarPermission(String action, int pid, int uid) {
+ if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+ pid, uid) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Only system ui may " + action);
+ }
+ }
+
/**
* This checks if the component is an enabled notification listener for the
* specified user. Enabled components may only operate on behalf of the user
@@ -497,6 +515,7 @@
for (int i = 0; i < size; i++) {
tokens.add(new MediaSessionToken(records.get(i).getControllerBinder()));
}
+ pushRemoteVolumeUpdateLocked(userId);
for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
SessionsListenerRecord record = mSessionsListeners.get(i);
if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
@@ -512,6 +531,17 @@
}
}
+ private void pushRemoteVolumeUpdateLocked(int userId) {
+ if (mRvc != null) {
+ try {
+ MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId);
+ mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
+ }
+ }
+ }
+
private void persistMediaButtonReceiverLocked(MediaSessionRecord record) {
ComponentName receiver = record.getMediaButtonReceiver();
if (receiver != null) {
@@ -844,6 +874,19 @@
}
@Override
+ public void setRemoteVolumeController(IRemoteVolumeController rvc) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ enforceStatusBarPermission("listen for volume changes", pid, uid);
+ mRvc = rvc;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -929,6 +972,13 @@
}
} else {
session.adjustVolumeBy(delta, flags);
+ if (mRvc != null) {
+ try {
+ mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Error sending volume change to system UI.", e);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 144ccfa..e26a2eb 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -203,6 +203,19 @@
return null;
}
+ public MediaSessionRecord getDefaultRemoteSession(int userId) {
+ ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
+
+ int size = records.size();
+ for (int i = 0; i < size; i++) {
+ MediaSessionRecord record = records.get(i);
+ if (record.getPlaybackType() == MediaSession.PLAYBACK_TYPE_REMOTE) {
+ return record;
+ }
+ }
+ return null;
+ }
+
public void dump(PrintWriter pw, String prefix) {
ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0,
UserHandle.USER_ALL);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 825dc84..c7adb68 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1546,41 +1546,11 @@
Collections.sort(mNotificationList, mRankingComparator);
- final int currentUser;
- final long token = Binder.clearCallingIdentity();
- try {
- currentUser = ActivityManager.getCurrentUser();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
if (notification.icon != 0) {
- if (old != null && !old.isCanceled) {
- final long identity = Binder.clearCallingIdentity();
- try {
- mStatusBar.updateNotification(n);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- } else {
- final long identity = Binder.clearCallingIdentity();
- try {
- mStatusBar.addNotification(n);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
mListeners.notifyPostedLocked(n);
} else {
Slog.e(TAG, "Not posting notification with icon==0: " + notification);
if (old != null && !old.isCanceled) {
- final long identity = Binder.clearCallingIdentity();
- try {
- mStatusBar.removeNotification(r.getKey());
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
-
mListeners.notifyRemovedLocked(n);
}
// ATTENTION: in a future release we will bail out here
@@ -1992,12 +1962,6 @@
// status bar
if (r.getNotification().icon != 0) {
- final long identity = Binder.clearCallingIdentity();
- try {
- mStatusBar.removeNotification(r.getKey());
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
r.isCanceled = true;
mListeners.notifyRemovedLocked(r.sbn);
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 25ebfc0..65cb6c9 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -197,7 +197,8 @@
mainIntent.setPackage(packageName);
long ident = Binder.clearCallingIdentity();
try {
- List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent, 0,
+ List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent,
+ PackageManager.NO_CROSS_PROFILE, // We only want the apps for this user
user.getIdentifier());
return apps;
} finally {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7fc7d0d..cac27bc 100755
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3425,31 +3425,35 @@
// reader
synchronized (mPackages) {
final String pkgName = intent.getPackage();
+ boolean queryCrossProfile = (flags & PackageManager.NO_CROSS_PROFILE) == 0;
if (pkgName == null) {
- //Check if the intent needs to be forwarded to another user for this package
- ArrayList<ResolveInfo> crossProfileResult =
- queryIntentActivitiesCrossProfilePackage(
- intent, resolvedType, flags, userId);
- if (!crossProfileResult.isEmpty()) {
- // Skip the current profile
- return crossProfileResult;
- }
- List<ResolveInfo> result;
- List<CrossProfileIntentFilter> matchingFilters =
- getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
- // Check for results that need to skip the current profile.
- ResolveInfo resolveInfo = querySkipCurrentProfileIntents(matchingFilters, intent,
- resolvedType, flags, userId);
- if (resolveInfo != null) {
- result = new ArrayList<ResolveInfo>(1);
- result.add(resolveInfo);
- return result;
+ ResolveInfo resolveInfo = null;
+ if (queryCrossProfile) {
+ // Check if the intent needs to be forwarded to another user for this package
+ ArrayList<ResolveInfo> crossProfileResult =
+ queryIntentActivitiesCrossProfilePackage(
+ intent, resolvedType, flags, userId);
+ if (!crossProfileResult.isEmpty()) {
+ // Skip the current profile
+ return crossProfileResult;
+ }
+ List<CrossProfileIntentFilter> matchingFilters =
+ getMatchingCrossProfileIntentFilters(intent, resolvedType, userId);
+ // Check for results that need to skip the current profile.
+ resolveInfo = querySkipCurrentProfileIntents(matchingFilters, intent,
+ resolvedType, flags, userId);
+ if (resolveInfo != null) {
+ List<ResolveInfo> result = new ArrayList<ResolveInfo>(1);
+ result.add(resolveInfo);
+ return result;
+ }
+ // Check for cross profile results.
+ resolveInfo = queryCrossProfileIntents(
+ matchingFilters, intent, resolvedType, flags, userId);
}
// Check for results in the current profile.
- result = mActivities.queryIntent(intent, resolvedType, flags, userId);
- // Check for cross profile results.
- resolveInfo = queryCrossProfileIntents(
- matchingFilters, intent, resolvedType, flags, userId);
+ List<ResolveInfo> result = mActivities.queryIntent(
+ intent, resolvedType, flags, userId);
if (resolveInfo != null) {
result.add(resolveInfo);
}
@@ -3457,12 +3461,14 @@
}
final PackageParser.Package pkg = mPackages.get(pkgName);
if (pkg != null) {
- ArrayList<ResolveInfo> crossProfileResult =
- queryIntentActivitiesCrossProfilePackage(
- intent, resolvedType, flags, userId, pkg, pkgName);
- if (!crossProfileResult.isEmpty()) {
- // Skip the current profile
- return crossProfileResult;
+ if (queryCrossProfile) {
+ ArrayList<ResolveInfo> crossProfileResult =
+ queryIntentActivitiesCrossProfilePackage(
+ intent, resolvedType, flags, userId, pkg, pkgName);
+ if (!crossProfileResult.isEmpty()) {
+ // Skip the current profile
+ return crossProfileResult;
+ }
}
return mActivities.queryIntentForPackage(intent, resolvedType, flags,
pkg.activities, userId);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a0cb098..832a3b6 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1047,6 +1047,11 @@
}
private UserInfo createUserInternal(String name, int flags, int parentId) {
+ if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
+ UserManager.DISALLOW_ADD_USER, false)) {
+ Log.w(LOG_TAG, "Cannot add user. DISALLOW_ADD_USER is enabled.");
+ return null;
+ }
final long ident = Binder.clearCallingIdentity();
UserInfo userInfo = null;
try {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 8d905ba..dc44e51 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -23,7 +23,4 @@
public interface StatusBarManagerInternal {
void setNotificationDelegate(NotificationDelegate delegate);
- void addNotification(StatusBarNotification notification);
- void updateNotification(StatusBarNotification notification);
- void removeNotification(String key);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 55b5e3b..e548fa5 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -60,8 +60,6 @@
private NotificationDelegate mNotificationDelegate;
private volatile IStatusBar mBar;
private StatusBarIconList mIcons = new StatusBarIconList();
- private HashMap<String,StatusBarNotification> mNotifications
- = new HashMap<String,StatusBarNotification>();
// for disabling the status bar
private final ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>();
@@ -111,56 +109,7 @@
private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {
@Override
public void setNotificationDelegate(NotificationDelegate delegate) {
- synchronized (mNotifications) {
- mNotificationDelegate = delegate;
- }
- }
-
- @Override
- public void addNotification(StatusBarNotification notification) {
- synchronized (mNotifications) {
- mNotifications.put(notification.getKey(), notification);
- if (mBar != null) {
- try {
- mBar.addNotification(notification);
- } catch (RemoteException ex) {
- }
- }
- }
- }
-
- @Override
- public void updateNotification(StatusBarNotification notification) {
- synchronized (mNotifications) {
- String key = notification.getKey();
- if (!mNotifications.containsKey(key)) {
- throw new IllegalArgumentException("updateNotification key not found: " + key);
- }
- mNotifications.put(notification.getKey(), notification);
- if (mBar != null) {
- try {
- mBar.updateNotification(notification);
- } catch (RemoteException ex) {
- }
- }
- }
- }
-
- @Override
- public void removeNotification(String key) {
- synchronized (mNotifications) {
- final StatusBarNotification n = mNotifications.remove(key);
- if (n == null) {
- Slog.e(TAG, "removeNotification key not found: " + key);
- return;
- }
- if (mBar != null) {
- try {
- mBar.removeNotification(key);
- } catch (RemoteException ex) {
- }
- }
- }
+ mNotificationDelegate = delegate;
}
};
@@ -511,7 +460,7 @@
// ================================================================================
@Override
public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
- List<StatusBarNotification> notifications, int switches[], List<IBinder> binders) {
+ int switches[], List<IBinder> binders) {
enforceStatusBarService();
Slog.i(TAG, "registerStatusBar bar=" + bar);
@@ -519,11 +468,6 @@
synchronized (mIcons) {
iconList.copyFrom(mIcons);
}
- synchronized (mNotifications) {
- for (StatusBarNotification sbn : mNotifications.values()) {
- notifications.add(sbn);
- }
- }
synchronized (mLock) {
switches[0] = gatherDisableActionsLocked(mCurrentUserId);
switches[1] = mSystemUiVisibility;
@@ -708,15 +652,6 @@
mIcons.dump(pw);
}
- synchronized (mNotifications) {
- int i=0;
- pw.println("Notification list:");
- for (Map.Entry<String,StatusBarNotification> e: mNotifications.entrySet()) {
- pw.printf(" %2d: %s\n", i, e.getValue().toString());
- i++;
- }
- }
-
synchronized (mLock) {
pw.println(" mDisabled=0x" + Integer.toHexString(mDisabled));
final int N = mDisableRecords.size();
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 32546df..1aec569 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -88,6 +88,7 @@
private static final int MSG_UNREGISTER_LISTENER = 2;
private static final int MSG_DISPATCH_UNLOCK_ATTEMPT = 3;
private static final int MSG_ENABLED_AGENTS_CHANGED = 4;
+ private static final int MSG_REQUIRE_CREDENTIAL_ENTRY = 5;
private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<AgentInfo>();
private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<ITrustListener>();
@@ -314,6 +315,17 @@
}
}
+
+ private void requireCredentialEntry(int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ mUserHasAuthenticatedSinceBoot.clear();
+ updateTrustAll();
+ } else {
+ mUserHasAuthenticatedSinceBoot.put(userId, false);
+ updateTrust(userId);
+ }
+ }
+
// Listeners
private void addListener(ITrustListener listener) {
@@ -367,6 +379,17 @@
}
@Override
+ public void reportRequireCredentialEntry(int userId) throws RemoteException {
+ enforceReportPermission();
+ if (userId == UserHandle.USER_ALL || userId >= UserHandle.USER_OWNER) {
+ mHandler.obtainMessage(MSG_REQUIRE_CREDENTIAL_ENTRY, userId, 0).sendToTarget();
+ } else {
+ throw new IllegalArgumentException(
+ "userId must be an explicit user id or USER_ALL");
+ }
+ }
+
+ @Override
public void registerTrustListener(ITrustListener trustListener) throws RemoteException {
enforceListenerPermission();
mHandler.obtainMessage(MSG_REGISTER_LISTENER, trustListener).sendToTarget();
@@ -379,8 +402,8 @@
}
private void enforceReportPermission() {
- mContext.enforceCallingPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE,
- "reporting trust events");
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE, "reporting trust events");
}
private void enforceListenerPermission() {
@@ -460,6 +483,9 @@
case MSG_ENABLED_AGENTS_CHANGED:
refreshAgentList();
break;
+ case MSG_REQUIRE_CREDENTIAL_ENTRY:
+ requireCredentialEntry(msg.arg1);
+ break;
}
}
};
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 6798f3f..61fdaccb 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -214,6 +214,9 @@
if (DEBUG) Slog.d(TAG, "add " + info.getId());
userState.inputMap.put(info.getId(), info);
userState.packageList.add(si.packageName);
+
+ // Reconnect the service if existing input is updated.
+ updateServiceConnectionLocked(info.getId(), userId);
} catch (IOException | XmlPullParserException e) {
Slog.e(TAG, "Can't load TV input " + si.name, e);
}
@@ -353,6 +356,9 @@
Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(
userState.inputMap.get(inputId).getComponent());
+ // Binding service may fail if the service is updating.
+ // In that case, the connection will be revived in buildTvInputListLocked called by
+ // onSomePackagesChanged.
serviceState.mBound = mContext.bindServiceAsUser(
i, serviceState.mConnection, Context.BIND_AUTO_CREATE, new UserHandle(userId));
} else if (serviceState.mService != null && isStateEmpty) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2801f4f..893a891 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2255,9 +2255,12 @@
try {
LockPatternUtils utils = new LockPatternUtils(mContext);
utils.saveLockPassword(password, quality, false, userHandle);
+ boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0;
+ if (requireEntry) {
+ utils.requireCredentialEntry(UserHandle.USER_ALL);
+ }
synchronized (this) {
- int newOwner = (flags&DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY)
- != 0 ? callingUid : -1;
+ int newOwner = requireEntry ? callingUid : -1;
if (policy.mPasswordOwner != newOwner) {
policy.mPasswordOwner = newOwner;
saveSettingsLocked(userHandle);
@@ -2370,6 +2373,7 @@
getIPowerManager().goToSleep(SystemClock.uptimeMillis(),
PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN, 0);
// Ensure the device is locked
+ new LockPatternUtils(mContext).requireCredentialEntry(UserHandle.USER_ALL);
getWindowManager().lockNow(null);
} catch (RemoteException e) {
} finally {
@@ -3732,7 +3736,7 @@
/**
* Sets which packages may enter lock task mode.
*
- * This function can only be called by the device owner or the profile owner.
+ * This function can only be called by the device owner.
* @param components The list of components allowed to enter lock task mode.
*/
public void setLockTaskPackages(String[] packages) throws SecurityException {
@@ -3741,15 +3745,13 @@
String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
synchronized (this) {
- // Check whether any of the package name is the device owner or the profile owner.
+ // Check whether any of the package name is the device owner.
for (int i=0; i<packageNames.length; i++) {
String packageName = packageNames[i];
int userHandle = UserHandle.getUserId(uid);
- String profileOwnerPackage = getProfileOwner(userHandle);
- if (isDeviceOwner(packageName) ||
- (profileOwnerPackage != null && profileOwnerPackage.equals(packageName))) {
+ if (isDeviceOwner(packageName)) {
- // If a package name is the device owner or the profile owner,
+ // If a package name is the device owner,
// we update the component list.
DevicePolicyData policy = getUserData(userHandle);
policy.mLockTaskPackages.clear();
diff --git a/telecomm/java/android/telecomm/CallService.java b/telecomm/java/android/telecomm/CallService.java
index cf7c901..12ba1ff 100644
--- a/telecomm/java/android/telecomm/CallService.java
+++ b/telecomm/java/android/telecomm/CallService.java
@@ -27,8 +27,6 @@
import com.android.internal.telecomm.ICallService;
import com.android.internal.telecomm.ICallServiceAdapter;
-import java.util.List;
-
/**
* Base implementation of CallService which can be used to provide calls for the system
* in-call UI. CallService is a one-way service from the framework's CallsManager to any app
@@ -72,7 +70,7 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_CALL_SERVICE_ADAPTER:
- mAdapter = new CallServiceAdapter((ICallServiceAdapter) msg.obj);
+ mAdapter.addAdapter((ICallServiceAdapter) msg.obj);
onAdapterAttached(mAdapter);
break;
case MSG_CALL:
@@ -259,7 +257,7 @@
*/
private final CallServiceBinder mBinder = new CallServiceBinder();
- private CallServiceAdapter mAdapter = null;
+ private CallServiceAdapter mAdapter = new CallServiceAdapter();
/** {@inheritDoc} */
@Override
diff --git a/telecomm/java/android/telecomm/CallServiceAdapter.java b/telecomm/java/android/telecomm/CallServiceAdapter.java
index 2c5f078..31e37c4 100644
--- a/telecomm/java/android/telecomm/CallServiceAdapter.java
+++ b/telecomm/java/android/telecomm/CallServiceAdapter.java
@@ -16,38 +16,86 @@
package android.telecomm;
+import android.content.ComponentName;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
+import com.android.internal.telecomm.ICallService;
import com.android.internal.telecomm.ICallServiceAdapter;
+import com.android.internal.telecomm.RemoteServiceCallback;
+import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Provides methods for ICallService implementations to interact with the system phone app.
* TODO(santoscordon): Need final public-facing comments in this file.
+ * TODO(santoscordon): Rename this to CallServiceAdapterDemultiplexer (or something).
*/
-public final class CallServiceAdapter {
- private final ICallServiceAdapter mAdapter;
+public final class CallServiceAdapter implements DeathRecipient {
+ private final Set<ICallServiceAdapter> mAdapters = new HashSet<>();
/**
- * {@hide}
+ * @hide
*/
- public CallServiceAdapter(ICallServiceAdapter adapter) {
- mAdapter = adapter;
+ public CallServiceAdapter() {
+ }
+
+ /**
+ * @hide
+ */
+ public void addAdapter(ICallServiceAdapter adapter) {
+ if (mAdapters.add(adapter)) {
+ try {
+ adapter.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ mAdapters.remove(adapter);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void removeAdapter(ICallServiceAdapter adapter) {
+ if (mAdapters.remove(adapter)) {
+ adapter.asBinder().unlinkToDeath(this, 0);
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void binderDied() {
+ ICallServiceAdapter adapterToRemove = null;
+ for (ICallServiceAdapter adapter : mAdapters) {
+ if (!adapter.asBinder().isBinderAlive()) {
+ adapterToRemove = adapter;
+ break;
+ }
+ }
+
+ if (adapterToRemove != null) {
+ removeAdapter(adapterToRemove);
+ }
}
/**
* Provides Telecomm with the details of an incoming call. An invocation of this method must
- * follow {@link CallService#setIncomingCallId} and use the call ID specified therein. Upon
- * the invocation of this method, Telecomm will bring up the incoming-call interface where the
- * user can elect to answer or reject a call.
+ * follow {@link CallService#setIncomingCallId} and use the call ID specified therein. Upon the
+ * invocation of this method, Telecomm will bring up the incoming-call interface where the user
+ * can elect to answer or reject a call.
*
* @param callInfo The details of the relevant call.
*/
public void notifyIncomingCall(CallInfo callInfo) {
- try {
- mAdapter.notifyIncomingCall(callInfo);
- } catch (RemoteException e) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.notifyIncomingCall(callInfo);
+ } catch (RemoteException e) {
+ }
}
}
@@ -59,9 +107,11 @@
* @param callId The ID of the outgoing call.
*/
public void handleSuccessfulOutgoingCall(String callId) {
- try {
- mAdapter.handleSuccessfulOutgoingCall(callId);
- } catch (RemoteException e) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.handleSuccessfulOutgoingCall(callId);
+ } catch (RemoteException e) {
+ }
}
}
@@ -76,9 +126,25 @@
ConnectionRequest request,
int errorCode,
String errorMsg) {
- try {
- mAdapter.handleFailedOutgoingCall(request, errorCode, errorMsg);
- } catch (RemoteException e) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.handleFailedOutgoingCall(request, errorCode, errorMsg);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Tells Telecomm to cancel the call.
+ *
+ * @param callId The ID of the outgoing call.
+ */
+ public void cancelOutgoingCall(String callId) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.cancelOutgoingCall(callId);
+ } catch (RemoteException e) {
+ }
}
}
@@ -89,9 +155,11 @@
* @param callId The unique ID of the call whose state is changing to active.
*/
public void setActive(String callId) {
- try {
- mAdapter.setActive(callId);
- } catch (RemoteException e) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.setActive(callId);
+ } catch (RemoteException e) {
+ }
}
}
@@ -101,9 +169,11 @@
* @param callId The unique ID of the call whose state is changing to ringing.
*/
public void setRinging(String callId) {
- try {
- mAdapter.setRinging(callId);
- } catch (RemoteException e) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.setRinging(callId);
+ } catch (RemoteException e) {
+ }
}
}
@@ -113,9 +183,11 @@
* @param callId The unique ID of the call whose state is changing to dialing.
*/
public void setDialing(String callId) {
- try {
- mAdapter.setDialing(callId);
- } catch (RemoteException e) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.setDialing(callId);
+ } catch (RemoteException e) {
+ }
}
}
@@ -124,13 +196,15 @@
*
* @param callId The unique ID of the call whose state is changing to disconnected.
* @param disconnectCause The reason for the disconnection, any of
- * {@link android.telephony.DisconnectCause}.
+ * {@link android.telephony.DisconnectCause}.
* @param disconnectMessage Optional call-service-provided message about the disconnect.
*/
public void setDisconnected(String callId, int disconnectCause, String disconnectMessage) {
- try {
- mAdapter.setDisconnected(callId, disconnectCause, disconnectMessage);
- } catch (RemoteException e) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.setDisconnected(callId, disconnectCause, disconnectMessage);
+ } catch (RemoteException e) {
+ }
}
}
@@ -140,9 +214,11 @@
* @param callId - The unique ID of the call whose state is changing to be on hold.
*/
public void setOnHold(String callId) {
- try {
- mAdapter.setOnHold(callId);
- } catch (RemoteException e) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.setOnHold(callId);
+ } catch (RemoteException e) {
+ }
}
}
@@ -153,9 +229,11 @@
* @param ringback Whether Telecomm should start playing a ringback tone.
*/
public void setRequestingRingback(String callId, boolean ringback) {
- try {
- mAdapter.setRequestingRingback(callId, ringback);
- } catch (RemoteException e) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.setRequestingRingback(callId, ringback);
+ } catch (RemoteException e) {
+ }
}
}
@@ -167,9 +245,11 @@
* @hide
*/
public void setCanConference(String callId, boolean canConference) {
- try {
- mAdapter.setCanConference(callId, canConference);
- } catch (RemoteException ignored) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.setCanConference(callId, canConference);
+ } catch (RemoteException ignored) {
+ }
}
}
@@ -179,13 +259,15 @@
*
* @param callId The unique ID of the call being conferenced.
* @param conferenceCallId The unique ID of the conference call. Null if call is not
- * conferenced.
+ * conferenced.
* @hide
*/
public void setIsConferenced(String callId, String conferenceCallId) {
- try {
- mAdapter.setIsConferenced(callId, conferenceCallId);
- } catch (RemoteException ignored) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.setIsConferenced(callId, conferenceCallId);
+ } catch (RemoteException ignored) {
+ }
}
}
@@ -197,16 +279,20 @@
* @hide
*/
public void removeCall(String callId) {
- try {
- mAdapter.removeCall(callId);
- } catch (RemoteException ignored) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.removeCall(callId);
+ } catch (RemoteException ignored) {
+ }
}
}
public void onPostDialWait(String callId, String remaining) {
- try {
- mAdapter.onPostDialWait(callId, remaining);
- } catch (RemoteException ignored) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.onPostDialWait(callId, remaining);
+ } catch (RemoteException ignored) {
+ }
}
}
@@ -216,9 +302,11 @@
* @param callId The identifier of the call to handoff.
*/
public void handoffCall(String callId) {
- try {
- mAdapter.handoffCall(callId);
- } catch (RemoteException e) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.handoffCall(callId);
+ } catch (RemoteException e) {
+ }
}
}
@@ -228,9 +316,26 @@
* @param callId The unique ID of the conference call.
*/
public void addConferenceCall(String callId) {
- try {
- mAdapter.addConferenceCall(callId, null);
- } catch (RemoteException ignored) {
+ for (ICallServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.addConferenceCall(callId, null);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ /**
+ * Retrieves a list of remote connection services usable to place calls.
+ * @hide
+ */
+ public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
+ // Only supported when there is only one adapter.
+ if (mAdapters.size() == 1) {
+ try {
+ mAdapters.iterator().next().queryRemoteConnectionServices(callback);
+ } catch (RemoteException e) {
+ Log.e(this, e, "Exception trying to query for remote CSs");
+ }
}
}
}
diff --git a/telecomm/java/android/telecomm/CallVideoProvider.java b/telecomm/java/android/telecomm/CallVideoProvider.java
new file mode 100644
index 0000000..6126fca
--- /dev/null
+++ b/telecomm/java/android/telecomm/CallVideoProvider.java
@@ -0,0 +1,69 @@
+/*
+ * 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.telecomm;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+
+import com.android.internal.telecomm.ICallVideoProvider;
+
+/** @hide */
+public abstract class CallVideoProvider {
+ private static final int MSG_SET_CAMERA = 1;
+
+ /**
+ * Default handler used to consolidate binder method calls onto a single thread.
+ */
+ private final class CallVideoProviderHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SET_CAMERA:
+ setCamera((String) msg.obj);
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Default ICallVideoProvider implementation.
+ */
+ private final class CallVideoProviderBinder extends ICallVideoProvider.Stub {
+ public void setCamera(String cameraId) {
+ mMessageHandler.obtainMessage(MSG_SET_CAMERA, cameraId).sendToTarget();
+ }
+ }
+
+ private final CallVideoProviderHandler mMessageHandler = new CallVideoProviderHandler();
+ private final CallVideoProviderBinder mBinder;
+
+ protected CallVideoProvider() {
+ mBinder = new CallVideoProviderBinder();
+ }
+
+ /**
+ * Sets the camera to be used for video recording in a video call.
+ *
+ * @param cameraId The id of the camera.
+ */
+ public abstract void setCamera(String cameraId);
+}
diff --git a/telecomm/java/android/telecomm/CallVideoProviderWrapper.java b/telecomm/java/android/telecomm/CallVideoProviderWrapper.java
new file mode 100644
index 0000000..862adff
--- /dev/null
+++ b/telecomm/java/android/telecomm/CallVideoProviderWrapper.java
@@ -0,0 +1,46 @@
+/*
+ * 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.telecomm;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.telecomm.ICallVideoProvider;
+
+
+public class CallVideoProviderWrapper implements IBinder.DeathRecipient {
+ private final ICallVideoProvider mCallVideoProvider;
+
+ CallVideoProviderWrapper(ICallVideoProvider callVideoProvider) throws RemoteException {
+ mCallVideoProvider = callVideoProvider;
+ mCallVideoProvider.asBinder().linkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ mCallVideoProvider.asBinder().unlinkToDeath(this, 0);
+ }
+
+ /**
+ * Sets the camera to be used for video recording in a video call, using the binder.
+ *
+ * @param cameraId The id of the camera.
+ */
+ public void setCamera(String cameraId) throws RemoteException {
+ mCallVideoProvider.setCamera(cameraId);
+ };
+}
\ No newline at end of file
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index 1783327..e4992d0 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -123,8 +123,6 @@
/**
* @return The state of this Connection.
- *
- * @hide
*/
public final int getState() {
return mState;
@@ -275,6 +273,8 @@
/**
* TODO(santoscordon): Needs updated documentation.
+ *
+ * @hide
*/
public final void conference() {
Log.d(this, "conference");
@@ -285,9 +285,14 @@
* Inform this Connection that the state of its audio output has been changed externally.
*
* @param state The new audio state.
+ * @hide
*/
- public void setAudioState(CallAudioState state) {
+ public final void setAudioState(CallAudioState state) {
Log.d(this, "setAudioState %s", state);
+ mCallAudioState = state;
+ for (Listener l : mListeners) {
+ l.onAudioStateChanged(this, state);
+ }
onSetAudioState(state);
}
@@ -319,18 +324,21 @@
* Returns whether this connection is requesting that the system play a ringback tone
* on its behalf.
*/
- public boolean isRequestingRingback() {
+ public final boolean isRequestingRingback() {
return mRequestingRingback;
}
/**
* Returns whether this connection is a conference connection (has child connections).
*/
- public boolean isConferenceConnection() {
+ public final boolean isConferenceConnection() {
return !mChildConnections.isEmpty();
}
- public void setParentConnection(Connection parentConnection) {
+ /**
+ * TODO(santoscordon): Needs documentation.
+ */
+ public final void setParentConnection(Connection parentConnection) {
Log.d(this, "parenting %s to %s", this, parentConnection);
if (mParentConnection != parentConnection) {
if (mParentConnection != null) {
@@ -347,18 +355,18 @@
}
}
- public Connection getParentConnection() {
+ public final Connection getParentConnection() {
return mParentConnection;
}
- public List<Connection> getChildConnections() {
+ public final List<Connection> getChildConnections() {
return mChildConnections;
}
/**
* Returns whether this connection is capable of being conferenced.
*/
- public boolean isConferenceCapable() {
+ public final boolean isConferenceCapable() {
return mIsConferenceCapable;
}
@@ -367,7 +375,7 @@
*
* @param handle The new handle.
*/
- protected void setHandle(Uri handle) {
+ public final void setHandle(Uri handle) {
Log.d(this, "setHandle %s", handle);
// TODO: Enforce super called
mHandle = handle;
@@ -380,7 +388,7 @@
* Sets state to active (e.g., an ongoing call where two or more parties can actively
* communicate).
*/
- protected void setActive() {
+ public final void setActive() {
setRequestingRingback(false);
setState(State.ACTIVE);
}
@@ -388,21 +396,21 @@
/**
* Sets state to ringing (e.g., an inbound ringing call).
*/
- protected void setRinging() {
+ public final void setRinging() {
setState(State.RINGING);
}
/**
* Sets state to dialing (e.g., dialing an outbound call).
*/
- protected void setDialing() {
+ public final void setDialing() {
setState(State.DIALING);
}
/**
* Sets state to be on hold.
*/
- protected void setOnHold() {
+ public final void setOnHold() {
setState(State.HOLDING);
}
@@ -416,7 +424,7 @@
* {@link android.telephony.DisconnectCause}.
* @param message Optional call-service-provided message about the disconnect.
*/
- protected void setDisconnected(int cause, String message) {
+ public final void setDisconnected(int cause, String message) {
setState(State.DISCONNECTED);
Log.d(this, "Disconnected with cause %d message %s", cause, message);
for (Listener l : mListeners) {
@@ -430,7 +438,7 @@
*
* @param ringback Whether the ringback tone is to be played.
*/
- protected void setRequestingRingback(boolean ringback) {
+ public final void setRequestingRingback(boolean ringback) {
if (mRequestingRingback != ringback) {
mRequestingRingback = ringback;
for (Listener l : mListeners) {
@@ -442,7 +450,7 @@
/**
* TODO(santoscordon): Needs documentation.
*/
- protected void setIsConferenceCapable(boolean isConferenceCapable) {
+ public final void setIsConferenceCapable(boolean isConferenceCapable) {
if (mIsConferenceCapable != isConferenceCapable) {
mIsConferenceCapable = isConferenceCapable;
for (Listener l : mListeners) {
@@ -454,7 +462,7 @@
/**
* TODO(santoscordon): Needs documentation.
*/
- protected void setDestroyed() {
+ public final void setDestroyed() {
// It is possible that onDestroy() will trigger the listener to remove itself which will
// result in a concurrent modification exception. To counteract this we make a copy of the
// listeners and iterate on that.
@@ -466,46 +474,32 @@
}
/**
- * Notifies this Connection and listeners that the {@link #getCallAudioState()} property
- * has a new value.
- *
- * @param state The new call audio state.
- */
- protected void onSetAudioState(CallAudioState state) {
- // TODO: Enforce super called
- mCallAudioState = state;
- for (Listener l : mListeners) {
- l.onAudioStateChanged(this, state);
- }
- }
-
- /**
* Notifies this Connection and listeners of a change in the current signal levels
* for the underlying data transport.
*
* @param details A {@link android.os.Bundle} containing details of the current level.
*/
- protected void onSetSignal(Bundle details) {
- // TODO: Enforce super called
+ public final void setSignal(Bundle details) {
for (Listener l : mListeners) {
l.onSignalChanged(this, details);
}
}
/**
+ * Notifies this Connection and listeners that the {@link #getCallAudioState()} property
+ * has a new value.
+ *
+ * @param state The new call audio state.
+ */
+ protected void onSetAudioState(CallAudioState state) {}
+
+ /**
* Notifies this Connection of an internal state change. This method is called before the
- * state is actually changed. Overriding implementations must call
- * {@code super.onSetState(state)}.
+ * state is actually changed.
*
* @param state The new state, a {@link Connection.State} member.
*/
- protected void onSetState(int state) {
- // TODO: Enforce super called
- this.mState = state;
- for (Listener l : mListeners) {
- l.onStateChanged(this, state);
- }
- }
+ protected void onSetState(int state) {}
/**
* Notifies this Connection of a request to play a DTMF tone.
@@ -585,6 +579,10 @@
private void setState(int state) {
Log.d(this, "setState: %s", stateToString(state));
+ this.mState = state;
+ for (Listener l : mListeners) {
+ l.onStateChanged(this, state);
+ }
onSetState(state);
}
}
diff --git a/telecomm/java/android/telecomm/ConnectionRequest.java b/telecomm/java/android/telecomm/ConnectionRequest.java
index bf5727b..61ac816 100644
--- a/telecomm/java/android/telecomm/ConnectionRequest.java
+++ b/telecomm/java/android/telecomm/ConnectionRequest.java
@@ -33,18 +33,29 @@
private final String mCallId;
private final Uri mHandle;
private final Bundle mExtras;
+ private final Subscription mSubscription;
public ConnectionRequest(Uri handle, Bundle extras) {
this(null, handle, extras);
}
public ConnectionRequest(String callId, Uri handle, Bundle extras) {
+ this(null, callId, handle, extras);
+ }
+
+ public ConnectionRequest(Subscription subscription, String callId, Uri handle, Bundle extras) {
mCallId = callId;
mHandle = handle;
mExtras = extras;
+ mSubscription = subscription;
}
/**
+ * The subscription which should be used to place the call.
+ */
+ public Subscription getSubscription() { return mSubscription; }
+
+ /**
* An identifier for this call.
*/
public String getCallId() { return mCallId; }
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 5aba941..23b99fa 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -16,12 +16,21 @@
package android.telecomm;
+import android.content.ComponentName;
import android.net.Uri;
import android.os.Bundle;
+
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
import android.telephony.DisconnectCause;
+import com.android.internal.telecomm.ICallService;
+import com.android.internal.telecomm.RemoteServiceCallback;
+
import java.util.Collection;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -36,6 +45,41 @@
// Mappings from Connections to IDs as understood by the current CallService implementation
private final Map<String, Connection> mConnectionById = new HashMap<>();
private final Map<Connection, String> mIdByConnection = new HashMap<>();
+ private final RemoteConnectionManager mRemoteConnectionManager = new RemoteConnectionManager();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ private SimpleResponse<Uri, List<Subscription>> mSubscriptionLookupResponse;
+ private Uri mSubscriptionLookupHandle;
+ private boolean mAreSubscriptionsInitialized = false;
+
+ /**
+ * A callback for providing the resuilt of creating a connection.
+ */
+ public interface OutgoingCallResponse<CONNECTION> {
+ /**
+ * Tells Telecomm that an attempt to place the specified outgoing call succeeded.
+ *
+ * @param request The original request.
+ * @param connection The connection.
+ */
+ void onSuccess(ConnectionRequest request, CONNECTION connection);
+
+ /**
+ * Tells Telecomm that an attempt to place the specified outgoing call failed.
+ *
+ * @param request The original request.
+ * @param code An integer code indicating the reason for failure.
+ * @param msg A message explaining the reason for failure.
+ */
+ void onFailure(ConnectionRequest request, int code, String msg);
+
+ /**
+ * Tells Telecomm to cancel the call.
+ *
+ * @param request The original request.
+ */
+ void onCancel(ConnectionRequest request);
+ }
private final Connection.Listener mConnectionListener = new Connection.Listener() {
@Override
@@ -121,30 +165,24 @@
callInfo.getId(),
callInfo.getHandle(),
callInfo.getExtras()),
- new Response<ConnectionRequest, Connection>() {
+ new OutgoingCallResponse<Connection>() {
@Override
- public void onResult(ConnectionRequest request, Connection... result) {
- if (result != null && result.length != 1) {
- Log.d(this, "adapter handleFailedOutgoingCall %s", callInfo);
- getAdapter().handleFailedOutgoingCall(
- request,
- DisconnectCause.ERROR_UNSPECIFIED,
- "Created " + result.length + " Connections, expected 1");
- for (Connection c : result) {
- c.abort();
- }
- } else {
- Log.d(this, "adapter handleSuccessfulOutgoingCall %s",
- callInfo.getId());
- getAdapter().handleSuccessfulOutgoingCall(callInfo.getId());
- addConnection(callInfo.getId(), result[0]);
- }
+ public void onSuccess(ConnectionRequest request, Connection connection) {
+ Log.d(this, "adapter handleSuccessfulOutgoingCall %s",
+ callInfo.getId());
+ getAdapter().handleSuccessfulOutgoingCall(callInfo.getId());
+ addConnection(callInfo.getId(), connection);
}
@Override
- public void onError(ConnectionRequest request, int code, String msg) {
+ public void onFailure(ConnectionRequest request, int code, String msg) {
getAdapter().handleFailedOutgoingCall(request, code, msg);
}
+
+ @Override
+ public void onCancel(ConnectionRequest request) {
+ getAdapter().cancelOutgoingCall(callInfo.getId());
+ }
}
);
}
@@ -314,6 +352,71 @@
}
/**
+ * @hide
+ */
+ @Override
+ protected void onAdapterAttached(CallServiceAdapter adapter) {
+ if (mAreSubscriptionsInitialized) {
+ // No need to query again if we already did it.
+ return;
+ }
+
+ getAdapter().queryRemoteConnectionServices(new RemoteServiceCallback.Stub() {
+ @Override
+ public void onResult(
+ final List<ComponentName> componentNames,
+ final List<IBinder> callServices) {
+ mHandler.post(new Runnable() {
+ @Override public void run() {
+ for (int i = 0; i < componentNames.size() && i < callServices.size(); i++) {
+ mRemoteConnectionManager.addConnectionService(
+ componentNames.get(i),
+ ICallService.Stub.asInterface(callServices.get(i)));
+ }
+ mAreSubscriptionsInitialized = true;
+ Log.d(this, "remote call services found: " + callServices);
+ maybeRespondToSubscriptionLookup();
+ }
+ });
+ }
+
+ @Override
+ public void onError() {
+ mHandler.post(new Runnable() {
+ @Override public void run() {
+ mAreSubscriptionsInitialized = true;
+ maybeRespondToSubscriptionLookup();
+ }
+ });
+ }
+ });
+ }
+
+ public void lookupRemoteSubscriptions(
+ Uri handle, SimpleResponse<Uri, List<Subscription>> response) {
+ mSubscriptionLookupResponse = response;
+ mSubscriptionLookupHandle = handle;
+ maybeRespondToSubscriptionLookup();
+ }
+
+ public void maybeRespondToSubscriptionLookup() {
+ if (mAreSubscriptionsInitialized && mSubscriptionLookupResponse != null) {
+ mSubscriptionLookupResponse.onResult(
+ mSubscriptionLookupHandle,
+ mRemoteConnectionManager.getSubscriptions(mSubscriptionLookupHandle));
+
+ mSubscriptionLookupHandle = null;
+ mSubscriptionLookupResponse = null;
+ }
+ }
+
+ public void createRemoteOutgoingConnection(
+ ConnectionRequest request,
+ OutgoingCallResponse<RemoteConnection> response) {
+ mRemoteConnectionManager.createOutgoingConnection(request, response);
+ }
+
+ /**
* Returns all connections currently associated with this connection service.
*/
public Collection<Connection> getAllConnections() {
@@ -328,7 +431,7 @@
*/
public void onCreateConnections(
ConnectionRequest request,
- Response<ConnectionRequest, Connection> callback) {}
+ OutgoingCallResponse<Connection> callback) {}
/**
* Returns a new or existing conference connection when the the user elects to convert the
diff --git a/telecomm/java/android/telecomm/RemoteConnection.java b/telecomm/java/android/telecomm/RemoteConnection.java
new file mode 100644
index 0000000..92db0d8
--- /dev/null
+++ b/telecomm/java/android/telecomm/RemoteConnection.java
@@ -0,0 +1,227 @@
+/*
+ * 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.telecomm;
+
+import android.net.Uri;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.telephony.DisconnectCause;
+
+import com.android.internal.telecomm.ICallService;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * RemoteConnection object used by RemoteConnectionService.
+ */
+public final class RemoteConnection {
+ public interface Listener {
+ void onStateChanged(RemoteConnection connection, int state);
+ void onAudioStateChanged(RemoteConnection connection, CallAudioState state);
+ void onDisconnected(RemoteConnection connection, int cause, String message);
+ void onRequestingRingback(RemoteConnection connection, boolean ringback);
+ void onPostDialWait(RemoteConnection connection, String remainingDigits);
+ void onDestroyed(RemoteConnection connection);
+ }
+
+ private final ICallService mCallService;
+ private final String mConnectionId;
+ private final Set<Listener> mListeners = new HashSet<>();
+
+ private int mState;
+ private int mDisconnectCause = DisconnectCause.NOT_VALID;
+ private String mDisconnectMessage;
+ private boolean mRequestingRingback;
+ private boolean mConnected;
+
+ /**
+ * @hide
+ */
+ RemoteConnection(ICallService callService, String connectionId) {
+ mCallService = callService;
+ mConnectionId = connectionId;
+
+ mConnected = true;
+ }
+
+ public void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ public void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
+ public int getDisconnectCause() {
+ return mDisconnectCause;
+ }
+
+ public String getDisconnectMessage() {
+ return mDisconnectMessage;
+ }
+
+ public void answer() {
+ try {
+ if (mConnected) {
+ mCallService.answer(mConnectionId);
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ public void reject() {
+ try {
+ if (mConnected) {
+ mCallService.reject(mConnectionId);
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ public void hold() {
+ try {
+ if (mConnected) {
+ mCallService.hold(mConnectionId);
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ public void unhold() {
+ try {
+ if (mConnected) {
+ mCallService.unhold(mConnectionId);
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ public void disconnect() {
+ try {
+ if (mConnected) {
+ mCallService.disconnect(mConnectionId);
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ public void playDtmf(char digit) {
+ try {
+ if (mConnected) {
+ mCallService.playDtmfTone(mConnectionId, digit);
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ public void stopDtmf() {
+ try {
+ if (mConnected) {
+ mCallService.stopDtmfTone(mConnectionId);
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ public void postDialContinue(boolean proceed) {
+ try {
+ if (mConnected) {
+ mCallService.onPostDialContinue(mConnectionId, proceed);
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ void setState(int state) {
+ if (mState != state) {
+ mState = state;
+ for (Listener l: mListeners) {
+ l.onStateChanged(this, state);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ void setAudioState(CallAudioState state) {
+ for (Listener l: mListeners) {
+ l.onAudioStateChanged(this, state);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ void setDisconnected(int cause, String message) {
+ if (mState != Connection.State.DISCONNECTED) {
+ mState = Connection.State.DISCONNECTED;
+ mDisconnectCause = cause;
+ mDisconnectMessage = message;
+
+ for (Listener l : mListeners) {
+ l.onDisconnected(this, cause, message);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ void setRequestingRingback(boolean ringback) {
+ if (mRequestingRingback != ringback) {
+ mRequestingRingback = ringback;
+ for (Listener l : mListeners) {
+ l.onRequestingRingback(this, ringback);
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ void setDestroyed() {
+ if (!mListeners.isEmpty()) {
+ // Make sure that the listeners are notified that the call is destroyed first.
+ if (mState != Connection.State.DISCONNECTED) {
+ setDisconnected(DisconnectCause.ERROR_UNSPECIFIED, "Connection destroyed.");
+ }
+
+ Set<Listener> listeners = new HashSet<Listener>(mListeners);
+ mListeners.clear();
+ for (Listener l : listeners) {
+ l.onDestroyed(this);
+ }
+
+ mConnected = false;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ void setPostDialWait(String remainingDigits) {
+ for (Listener l : mListeners) {
+ l.onPostDialWait(this, remainingDigits);
+ }
+ }
+}
diff --git a/telecomm/java/android/telecomm/RemoteConnectionManager.java b/telecomm/java/android/telecomm/RemoteConnectionManager.java
new file mode 100644
index 0000000..465fae0
--- /dev/null
+++ b/telecomm/java/android/telecomm/RemoteConnectionManager.java
@@ -0,0 +1,74 @@
+/*
+ * 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
+ R* limitations under the License.
+ */
+
+package android.telecomm;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.telecomm.ICallService;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public class RemoteConnectionManager {
+ private Map<ComponentName, RemoteConnectionService> mRemoteConnectionServices = new HashMap<>();
+
+ void addConnectionService(ComponentName componentName, ICallService callService) {
+ if (!mRemoteConnectionServices.containsKey(componentName)) {
+ try {
+ RemoteConnectionService remoteConnectionService =
+ new RemoteConnectionService(componentName, callService);
+ mRemoteConnectionServices.put(componentName, remoteConnectionService);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ List<Subscription> getSubscriptions(Uri handle) {
+ List<Subscription> subscriptions = new LinkedList<>();
+ Log.d(this, "Getting subscriptions: " + mRemoteConnectionServices.keySet());
+ for (RemoteConnectionService remoteService : mRemoteConnectionServices.values()) {
+ // TODO(santoscordon): Eventually this will be async.
+ subscriptions.addAll(remoteService.lookupSubscriptions(handle));
+ }
+ return subscriptions;
+ }
+
+ public void createOutgoingConnection(
+ ConnectionRequest request,
+ final ConnectionService.OutgoingCallResponse response) {
+ Subscription subscription = request.getSubscription();
+ if (subscription == null) {
+ throw new IllegalArgumentException("subscription must be specified.");
+ }
+
+ ComponentName componentName = request.getSubscription().getComponentName();
+ if (!mRemoteConnectionServices.containsKey(componentName)) {
+ throw new UnsupportedOperationException("subscription not supported: " + componentName);
+ } else {
+ RemoteConnectionService remoteService = mRemoteConnectionServices.get(componentName);
+ remoteService.createOutgoingConnection(request, response);
+ }
+ }
+}
diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java
new file mode 100644
index 0000000..7658a76
--- /dev/null
+++ b/telecomm/java/android/telecomm/RemoteConnectionService.java
@@ -0,0 +1,284 @@
+/*
+ * 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
+ R* limitations under the License.
+ */
+
+package android.telecomm;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.telephony.DisconnectCause;
+
+import android.text.TextUtils;
+
+import com.android.internal.telecomm.ICallService;
+import com.android.internal.telecomm.ICallServiceAdapter;
+import com.android.internal.telecomm.RemoteServiceCallback;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Remote connection service which other connection services can use to place calls on their behalf.
+ */
+public class RemoteConnectionService implements DeathRecipient {
+ private final ICallService mCallService;
+ private final ComponentName mComponentName;
+
+ private String mConnectionId;
+ private ConnectionRequest mPendingRequest;
+ private ConnectionService.OutgoingCallResponse<RemoteConnection> mPendingOutgoingCallResponse;
+ // Remote connection services only support a single connection.
+ private RemoteConnection mConnection;
+
+ private final ICallServiceAdapter mAdapter = new ICallServiceAdapter.Stub() {
+
+ /** ${inheritDoc} */
+ @Override
+ public void notifyIncomingCall(CallInfo callInfo) {
+ Log.w(this, "notifyIncomingCall not implemented in Remote connection");
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void handleSuccessfulOutgoingCall(String connectionId) {
+ if (isPendingConnection(connectionId)) {
+ mConnection = new RemoteConnection(mCallService, connectionId);
+ mPendingOutgoingCallResponse.onSuccess(mPendingRequest, mConnection);
+ clearPendingInformation();
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void handleFailedOutgoingCall(
+ ConnectionRequest request, int errorCode, String errorMessage) {
+ if (isPendingConnection(request.getCallId())) {
+ // Use mPendingRequest instead of request so that we use the same object that was
+ // passed in to us.
+ mPendingOutgoingCallResponse.onFailure(mPendingRequest, errorCode, errorMessage);
+ mConnectionId = null;
+ clearPendingInformation();
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void cancelOutgoingCall(String connectionId) {
+ if (isPendingConnection(connectionId)) {
+ mPendingOutgoingCallResponse.onCancel(mPendingRequest);
+ mConnectionId = null;
+ clearPendingInformation();
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void setActive(String connectionId) {
+ if (isCurrentConnection(connectionId)) {
+ mConnection.setState(Connection.State.ACTIVE);
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void setRinging(String connectionId) {
+ if (isCurrentConnection(connectionId)) {
+ mConnection.setState(Connection.State.RINGING);
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void setDialing(String connectionId) {
+ if (isCurrentConnection(connectionId)) {
+ mConnection.setState(Connection.State.DIALING);
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void setDisconnected(
+ String connectionId, int disconnectCause, String disconnectMessage) {
+ if (isCurrentConnection(connectionId)) {
+ mConnection.setDisconnected(disconnectCause, disconnectMessage);
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void setOnHold(String connectionId) {
+ if (isCurrentConnection(connectionId)) {
+ mConnection.setState(Connection.State.HOLDING);
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void setRequestingRingback(String connectionId, boolean isRequestingRingback) {
+ if (isCurrentConnection(connectionId)) {
+ mConnection.setRequestingRingback(isRequestingRingback);
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void setCanConference(String connectionId, boolean canConference) {
+ // not supported for remote connections.
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void setIsConferenced(String connectionId, String conferenceConnectionId) {
+ // not supported for remote connections.
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void addConferenceCall(String connectionId, CallInfo callInfo) {
+ // not supported for remote connections.
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void removeCall(String connectionId) {
+ if (isCurrentConnection(connectionId)) {
+ destroyConnection();
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void onPostDialWait(String connectionId, String remainingDigits) {
+ if (isCurrentConnection(connectionId)) {
+ mConnection.setPostDialWait(remainingDigits);
+ }
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void handoffCall(String connectionId) {
+ // unnecessary.
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
+ try {
+ // Not supported from remote connection service.
+ callback.onError();
+ } catch (RemoteException e) {
+ }
+ }
+ };
+
+ RemoteConnectionService(ComponentName componentName, ICallService callService)
+ throws RemoteException {
+ mComponentName = componentName;
+ mCallService = callService;
+
+ // TODO(santoscordon): Rename from setCallServiceAdapter to addCallServiceAdapter.
+ mCallService.setCallServiceAdapter(mAdapter);
+ mCallService.asBinder().linkToDeath(this, 0);
+ }
+
+ @Override
+ public String toString() {
+ return "[RemoteCS - " + mCallService.asBinder().toString() + "]";
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void binderDied() {
+ if (mConnection != null) {
+ destroyConnection();
+ }
+
+ release();
+ }
+
+ /**
+ * Places an outgoing call.
+ */
+ public void createOutgoingConnection(
+ ConnectionRequest request,
+ ConnectionService.OutgoingCallResponse<RemoteConnection> response) {
+
+ if (mConnectionId == null) {
+ String id = UUID.randomUUID().toString();
+ CallInfo callInfo = new CallInfo(id, CallState.NEW, request.getHandle());
+ try {
+ mCallService.call(callInfo);
+ mConnectionId = id;
+ mPendingOutgoingCallResponse = response;
+ mPendingRequest = request;
+ } catch (RemoteException e) {
+ response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, e.toString());
+ }
+ } else {
+ response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, null);
+ }
+ }
+
+ // TODO(santoscordon): Handle incoming connections
+ // public void handleIncomingConnection() {}
+
+ public List<Subscription> lookupSubscriptions(Uri handle) {
+ // TODO(santoscordon): Update this so that is actually calls into the RemoteConnection
+ // each time.
+ List<Subscription> subscriptions = new LinkedList<>();
+ subscriptions.add(new Subscription(
+ mComponentName,
+ null /* id */,
+ null /* handle */,
+ 0 /* labelResId */,
+ 0 /* shortDescriptionResId */,
+ 0 /* iconResId */,
+ true /* isEnabled */,
+ false /* isSystemDefault */));
+ return subscriptions;
+ }
+
+ /**
+ * Releases the resources associated with this Remote connection service. Should be called when
+ * the remote service is no longer being used.
+ */
+ void release() {
+ mCallService.asBinder().unlinkToDeath(this, 0);
+ }
+
+ private boolean isPendingConnection(String id) {
+ return TextUtils.equals(mConnectionId, id) && mPendingOutgoingCallResponse != null;
+ }
+
+ private boolean isCurrentConnection(String id) {
+ return mConnection != null && TextUtils.equals(mConnectionId, id);
+ }
+
+ private void clearPendingInformation() {
+ mPendingRequest = null;
+ mPendingOutgoingCallResponse = null;
+ }
+
+ private void destroyConnection() {
+ mConnection.setDestroyed();
+ mConnection = null;
+ mConnectionId = null;
+ }
+}
diff --git a/telecomm/java/android/telecomm/SimpleResponse.java b/telecomm/java/android/telecomm/SimpleResponse.java
new file mode 100644
index 0000000..8e84adb
--- /dev/null
+++ b/telecomm/java/android/telecomm/SimpleResponse.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecomm;
+
+/**
+ * Used to inform a client of asynchronously returned results.
+ */
+public interface SimpleResponse<IN, OUT> {
+
+ /**
+ * Provide a set of results.
+ *
+ * @param request The original request.
+ * @param result The results.
+ */
+ void onResult(IN request, OUT result);
+
+ /**
+ * Indicates the inability to provide results.
+ *
+ * @param request The original request.
+ */
+ void onError(IN request);
+}
diff --git a/telecomm/java/android/telecomm/TelecommManager.java b/telecomm/java/android/telecomm/TelecommManager.java
index a97e7e4..a0abc28 100644
--- a/telecomm/java/android/telecomm/TelecommManager.java
+++ b/telecomm/java/android/telecomm/TelecommManager.java
@@ -16,7 +16,12 @@
package android.telecomm;
+import android.annotation.SystemApi;
+import android.content.ComponentName;
import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
import com.android.internal.telecomm.ITelecommService;
@@ -25,11 +30,14 @@
*/
public class TelecommManager {
private static final String TAG = "TelecommManager";
+ private static final String TELECOMM_SERVICE_NAME = "telecomm";
private final Context mContext;
private final ITelecommService mService;
- /** @hide */
+ /**
+ * @hide
+ */
public TelecommManager(Context context, ITelecommService service) {
Context appContext = context.getApplicationContext();
if (appContext != null) {
@@ -41,8 +49,27 @@
mService = service;
}
- /** {@hide} */
+ /**
+ * @hide
+ */
public static TelecommManager from(Context context) {
return (TelecommManager) context.getSystemService(Context.TELECOMM_SERVICE);
}
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public ComponentName getDefaultPhoneApp() {
+ try {
+ return getTelecommService().getDefaultPhoneApp();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException attempting to get the default phone app.", e);
+ }
+ return null;
+ }
+
+ private ITelecommService getTelecommService() {
+ return ITelecommService.Stub.asInterface(ServiceManager.getService(TELECOMM_SERVICE_NAME));
+ }
}
diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
index 270c551..373fb16 100644
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
@@ -19,6 +19,8 @@
import android.telecomm.CallInfo;
import android.telecomm.ConnectionRequest;
+import com.android.internal.telecomm.RemoteServiceCallback;
+
/**
* Internal remote callback interface for call services.
*
@@ -33,6 +35,8 @@
void handleFailedOutgoingCall(in ConnectionRequest request, int errorCode, String errorMessage);
+ void cancelOutgoingCall(String callId);
+
void setActive(String callId);
void setRinging(String callId);
@@ -56,4 +60,6 @@
void onPostDialWait(String callId, String remaining);
void handoffCall(String callId);
+
+ void queryRemoteConnectionServices(RemoteServiceCallback callback);
}
diff --git a/telecomm/java/com/android/internal/telecomm/ICallVideoProvider.aidl b/telecomm/java/com/android/internal/telecomm/ICallVideoProvider.aidl
new file mode 100644
index 0000000..c116dcb
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecomm/ICallVideoProvider.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telecomm;
+
+/**
+ * Internal remote interface for a call video provider.
+ * @see android.telecomm.CallVideoProvider
+ * @hide
+ */
+oneway interface ICallVideoProvider {
+ void setCamera(String cameraId);
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
index c758c6d..2ae5768 100644
--- a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl
@@ -16,6 +16,7 @@
package com.android.internal.telecomm;
+import android.content.ComponentName;
import android.telecomm.Subscription;
/**
@@ -55,4 +56,9 @@
* Sets a given Subscription as the system default.
*/
void setSystemDefault(in Subscription subscription);
+
+ /**
+ * Returns the component name of the default phone application.
+ */
+ ComponentName getDefaultPhoneApp();
}
diff --git a/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl b/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl
new file mode 100644
index 0000000..42c77d7
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telecomm;
+
+import android.content.ComponentName;
+
+/**
+ * Simple response callback object.
+ */
+oneway interface RemoteServiceCallback {
+ void onError();
+ void onResult(in List<ComponentName> components, in List<IBinder> callServices);
+}
diff --git a/telephony/java/com/android/ims/ImsCallForwardInfo.aidl b/telephony/java/com/android/ims/ImsCallForwardInfo.aidl
new file mode 100644
index 0000000..a7c3f9a
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsCallForwardInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims;
+
+parcelable ImsCallForwardInfo;
diff --git a/telephony/java/com/android/ims/ImsCallForwardInfo.java b/telephony/java/com/android/ims/ImsCallForwardInfo.java
new file mode 100644
index 0000000..3f8fd19
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsCallForwardInfo.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Provides the call forward information for the supplementary service configuration.
+ *
+ * @hide
+ */
+public class ImsCallForwardInfo implements Parcelable {
+ // Refer to ImsUtInterface#CDIV_CF_XXX
+ public int mCondition;
+ // 0: disabled, 1: enabled
+ public int mStatus;
+ // 0x91: International, 0x81: Unknown
+ public int mToA;
+ // Number (it will not include the "sip" or "tel" URI scheme)
+ public String mNumber;
+ // No reply timer for CF
+ public int mTimeSeconds;
+
+ public ImsCallForwardInfo() {
+ }
+
+ public ImsCallForwardInfo(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mCondition);
+ out.writeInt(mStatus);
+ out.writeInt(mToA);
+ out.writeString(mNumber);
+ out.writeInt(mTimeSeconds);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ", Condition: " + mCondition
+ + ", Status: " + ((mStatus == 0) ? "disabled" : "enabled")
+ + ", ToA: " + mToA + ", Number=" + mNumber
+ + ", Time (seconds): " + mTimeSeconds;
+ }
+
+ private void readFromParcel(Parcel in) {
+ mCondition = in.readInt();
+ mStatus = in.readInt();
+ mToA = in.readInt();
+ mNumber = in.readString();
+ mTimeSeconds = in.readInt();
+ }
+
+ public static final Creator<ImsCallForwardInfo> CREATOR =
+ new Creator<ImsCallForwardInfo>() {
+ @Override
+ public ImsCallForwardInfo createFromParcel(Parcel in) {
+ return new ImsCallForwardInfo(in);
+ }
+
+ @Override
+ public ImsCallForwardInfo[] newArray(int size) {
+ return new ImsCallForwardInfo[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/ims/ImsCallProfile.aidl b/telephony/java/com/android/ims/ImsCallProfile.aidl
new file mode 100644
index 0000000..a356d13
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsCallProfile.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims;
+
+parcelable ImsCallProfile;
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
new file mode 100644
index 0000000..208f467
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Parcelable object to handle IMS call profile.
+ * It is created from GSMA IR.92/IR.94, 3GPP TS 24.229/TS 26.114/TS26.111.
+ * It provides the service and call type, the additional information related to the call.
+ *
+ * @hide
+ */
+public class ImsCallProfile implements Parcelable {
+ private static final String TAG = "ImsCallProfile";
+
+ /**
+ * Service types
+ */
+ /**
+ * It is for a special case. It helps that the application can make a call
+ * without IMS connection (not registered).
+ * In the moment of the call initiation, the device try to connect to the IMS network
+ * and initiates the call.
+ */
+ public static final int SERVICE_TYPE_NONE = 0;
+ /**
+ * It is a default type and can be selected when the device is connected to the IMS network.
+ */
+ public static final int SERVICE_TYPE_NORMAL = 1;
+ /**
+ * It is for an emergency call.
+ */
+ public static final int SERVICE_TYPE_EMERGENCY = 2;
+
+ /**
+ * Call types
+ */
+ /**
+ * IMSPhone to support IR.92 & IR.94 (voice + video upgrade/downgrade)
+ */
+ public static final int CALL_TYPE_VOICE_N_VIDEO = 1;
+ /**
+ * IR.92 (Voice only)
+ */
+ public static final int CALL_TYPE_VOICE = 2;
+ /**
+ * VT to support IR.92 & IR.94 (voice + video upgrade/downgrade)
+ */
+ public static final int CALL_TYPE_VIDEO_N_VOICE = 3;
+ /**
+ * Video Telephony (audio / video two way)
+ */
+ public static final int CALL_TYPE_VT = 4;
+ /**
+ * Video Telephony (audio two way / video TX one way)
+ */
+ public static final int CALL_TYPE_VT_TX = 5;
+ /**
+ * Video Telephony (audio two way / video RX one way)
+ */
+ public static final int CALL_TYPE_VT_RX = 6;
+ /**
+ * Video Telephony (audio two way / video inactive)
+ */
+ public static final int CALL_TYPE_VT_NODIR = 7;
+ /**
+ * VideoShare (video two way)
+ */
+ public static final int CALL_TYPE_VS = 8;
+ /**
+ * VideoShare (video TX one way)
+ */
+ public static final int CALL_TYPE_VS_TX = 9;
+ /**
+ * VideoShare (video RX one way)
+ */
+ public static final int CALL_TYPE_VS_RX = 10;
+
+ /**
+ * Extra properties for IMS call.
+ */
+ /**
+ * Boolean extra properties - "true" / "false"
+ * conference : Indicates if the session is for the conference call or not.
+ * e_call : Indicates if the session is for the emergency call or not.
+ * vms : Indicates if the session is connected to the voice mail system or not.
+ * call_mode_changeable : Indicates if the session is able to upgrade/downgrade
+ * the video during voice call.
+ * conference_avail : Indicates if the session can be extended to the conference.
+ */
+ public static final String EXTRA_CONFERENCE = "conference";
+ public static final String EXTRA_E_CALL = "e_call";
+ public static final String EXTRA_VMS = "vms";
+ public static final String EXTRA_CALL_MODE_CHANGEABLE = "call_mode_changeable";
+ public static final String EXTRA_CONFERENCE_AVAIL = "conference_avail";
+
+ /**
+ * Integer extra properties
+ * oir : Rule for originating identity (number) presentation, MO/MT.
+ * {@link ImsCallProfile#OIR_DEFAULT}
+ * {@link ImsCallProfile#OIR_PRESENTATION_RESTRICTED}
+ * {@link ImsCallProfile#OIR_PRESENTATION_NOT_RESTRICTED}
+ * cnap : Rule for calling name presentation
+ * {@link ImsCallProfile#OIR_DEFAULT}
+ * {@link ImsCallProfile#OIR_PRESENTATION_RESTRICTED}
+ * {@link ImsCallProfile#OIR_PRESENTATION_NOT_RESTRICTED}
+ * dialstring : To identify the Ims call type, MO
+ * {@link ImsCallProfile#DIALSTRING_NORMAL_CALL}
+ * {@link ImsCallProfile#DIALSTRING_SS_CONF}
+ * {@link ImsCallProfile#DIALSTRING_USSD}
+ */
+ public static final String EXTRA_OIR = "oir";
+ public static final String EXTRA_CNAP = "cnap";
+ public static final String EXTRA_DIALSTRING = "dialstring";
+
+ /**
+ * Values for EXTRA_OIR / EXTRA_CNAP
+ */
+ public static final int OIR_DEFAULT = 0; // "user subscription default value"
+ public static final int OIR_PRESENTATION_RESTRICTED = 1;
+ public static final int OIR_PRESENTATION_NOT_RESTRICTED = 2;
+
+ /**
+ * Values for EXTRA_DIALSTRING
+ */
+ // default (normal call)
+ public static final int DIALSTRING_NORMAL = 0;
+ // Call for SIP-based user configuration
+ public static final int DIALSTRING_SS_CONF = 1;
+ // Call for USSD message
+ public static final int DIALSTRING_USSD = 2;
+
+ /**
+ * String extra properties
+ * oi : Originating identity (number), MT only
+ * cna : Calling name
+ * ussd : For network-initiated USSD, MT only
+ * remote_uri : Connected user identity (it can be used for the conference)
+ */
+ public static final String EXTRA_OI = "oi";
+ public static final String EXTRA_CNA = "cna";
+ public static final String EXTRA_USSD = "ussd";
+ public static final String EXTRA_REMOTE_URI = "remote_uri";
+
+ public int mServiceType;
+ public int mCallType;
+ public Bundle mCallExtras;
+ public ImsStreamMediaProfile mMediaProfile;
+
+
+
+ public ImsCallProfile(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public ImsCallProfile() {
+ mServiceType = SERVICE_TYPE_NORMAL;
+ mCallType = CALL_TYPE_VOICE_N_VIDEO;
+ mCallExtras = new Bundle();
+ mMediaProfile = new ImsStreamMediaProfile();
+ }
+
+ public ImsCallProfile(int serviceType, int callType) {
+ mServiceType = serviceType;
+ mCallType = callType;
+ mCallExtras = new Bundle();
+ mMediaProfile = new ImsStreamMediaProfile();
+ }
+
+ public String getCallExtra(String name) {
+ return getCallExtra(name, "");
+ }
+
+ public String getCallExtra(String name, String defaultValue) {
+ if (mCallExtras == null) {
+ return defaultValue;
+ }
+
+ return mCallExtras.getString(name, defaultValue);
+ }
+
+ public boolean getCallExtraBoolean(String name) {
+ return getCallExtraBoolean(name, false);
+ }
+
+ public boolean getCallExtraBoolean(String name, boolean defaultValue) {
+ if (mCallExtras == null) {
+ return defaultValue;
+ }
+
+ return mCallExtras.getBoolean(name, defaultValue);
+ }
+
+ public int getCallExtraInt(String name) {
+ return getCallExtraInt(name, -1);
+ }
+
+ public int getCallExtraInt(String name, int defaultValue) {
+ if (mCallExtras == null) {
+ return defaultValue;
+ }
+
+ return mCallExtras.getInt(name, defaultValue);
+ }
+
+ public void setCallExtra(String name, String value) {
+ if (mCallExtras != null) {
+ mCallExtras.putString(name, value);
+ }
+ }
+
+ public void setCallExtraBoolean(String name, boolean value) {
+ if (mCallExtras != null) {
+ mCallExtras.putBoolean(name, value);
+ }
+ }
+
+ public void setCallExtraInt(String name, int value) {
+ if (mCallExtras != null) {
+ mCallExtras.putInt(name, value);
+ }
+ }
+
+ public void updateCallType(ImsCallProfile profile) {
+ mCallType = profile.mCallType;
+ }
+
+ public void updateCallExtras(ImsCallProfile profile) {
+ mCallExtras.clear();
+ mCallExtras = (Bundle) profile.mCallExtras.clone();
+ }
+
+ @Override
+ public String toString() {
+ return "{ serviceType=" + mServiceType +
+ ", callType=" + mCallType +
+ ", callExtras=" + mCallExtras.toString() +
+ ", mediaProfile=" + mMediaProfile.toString() + " }";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mServiceType);
+ out.writeInt(mCallType);
+ out.writeParcelable(mCallExtras, 0);
+ out.writeParcelable(mMediaProfile, 0);
+ }
+
+ private void readFromParcel(Parcel in) {
+ mServiceType = in.readInt();
+ mCallType = in.readInt();
+ mCallExtras = in.readParcelable(null);
+ mMediaProfile = in.readParcelable(null);
+ }
+
+ public static final Creator<ImsCallProfile> CREATOR = new Creator<ImsCallProfile>() {
+ @Override
+ public ImsCallProfile createFromParcel(Parcel in) {
+ return new ImsCallProfile(in);
+ }
+
+ @Override
+ public ImsCallProfile[] newArray(int size) {
+ return new ImsCallProfile[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/ims/ImsConferenceState.aidl b/telephony/java/com/android/ims/ImsConferenceState.aidl
new file mode 100644
index 0000000..2fc029f
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsConferenceState.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims;
+
+parcelable ImsConferenceState;
diff --git a/telephony/java/com/android/ims/ImsConferenceState.java b/telephony/java/com/android/ims/ImsConferenceState.java
new file mode 100644
index 0000000..f708d5b
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsConferenceState.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Provides the conference information (defined in RFC 4575) for IMS conference call.
+ *
+ * @hide
+ */
+public class ImsConferenceState implements Parcelable {
+ /**
+ * conference-info : user
+ */
+ // user (String) : Tel or SIP URI
+ public static final String USER = "user";
+ // user > display text (String)
+ public static final String DISPLAY_TEXT = "display-text";
+ // user > endpoint (String) : URI or GRUU or Phone number
+ public static final String ENDPOINT = "endpoint";
+ // user > endpoint > status
+ public static final String STATUS = "status";
+
+ /**
+ * status-type (String) :
+ * "pending" : Endpoint is not yet in the session, but it is anticipated that he/she will
+ * join in the near future.
+ * "dialing-out" : Focus has dialed out to connect the endpoint to the conference,
+ * but the endpoint is not yet in the roster (probably being authenticated).
+ * "dialing-in" : Endpoint is dialing into the conference, not yet in the roster
+ * (probably being authenticated).
+ * "alerting" : PSTN alerting or SIP 180 Ringing was returned for the outbound call;
+ * endpoint is being alerted.
+ * "on-hold" : Active signaling dialog exists between an endpoint and a focus,
+ * but endpoint is "on-hold" for this conference, i.e., he/she is neither "hearing"
+ * the conference mix nor is his/her media being mixed in the conference.
+ * "connected" : Endpoint is a participant in the conference. Depending on the media policies,
+ * he/she can send and receive media to and from other participants.
+ * "disconnecting" : Focus is in the process of disconnecting the endpoint
+ * (e.g. in SIP a DISCONNECT or BYE was sent to the endpoint).
+ * "disconnected" : Endpoint is not a participant in the conference, and no active dialog
+ * exists between the endpoint and the focus.
+ * "muted-via-focus" : Active signaling dialog exists beween an endpoint and a focus and
+ * the endpoint can "listen" to the conference, but the endpoint's media is not being
+ * mixed into the conference.
+ * "connect-fail" : Endpoint fails to join the conference by rejecting the conference call.
+ */
+ public static final String STATUS_PENDING = "pending";
+ public static final String STATUS_DIALING_OUT = "dialing-out";
+ public static final String STATUS_DIALING_IN = "dialing-in";
+ public static final String STATUS_ALERTING = "alerting";
+ public static final String STATUS_ON_HOLD = "on-hold";
+ public static final String STATUS_CONNECTED = "connected";
+ public static final String STATUS_DISCONNECTING = "disconnecting";
+ public static final String STATUS_DISCONNECTED = "disconnected";
+ public static final String STATUS_MUTED_VIA_FOCUS = "muted-via-focus";
+ public static final String STATUS_CONNECT_FAIL = "connect-fail";
+
+ /**
+ * conference-info : SIP status code (integer)
+ */
+ public static final String SIP_STATUS_CODE = "sipstatuscode";
+
+ public HashMap<String, Bundle> mParticipants = new HashMap<String, Bundle>();
+
+ public ImsConferenceState() {
+ }
+
+ public ImsConferenceState(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mParticipants.size());
+
+ if (mParticipants.size() > 0) {
+ Set<Entry<String, Bundle>> entries = mParticipants.entrySet();
+
+ if (entries != null) {
+ Iterator<Entry<String, Bundle>> iterator = entries.iterator();
+
+ while (iterator.hasNext()) {
+ Entry<String, Bundle> entry = iterator.next();
+
+ out.writeString(entry.getKey());
+ out.writeParcelable(entry.getValue(), 0);
+ }
+ }
+ }
+ }
+
+ private void readFromParcel(Parcel in) {
+ int size = in.readInt();
+
+ for (int i = 0; i < size; ++i) {
+ String user = in.readString();
+ Bundle state = in.readParcelable(null);
+ mParticipants.put(user, state);
+ }
+ }
+
+ public static final Creator<ImsConferenceState> CREATOR =
+ new Creator<ImsConferenceState>() {
+ @Override
+ public ImsConferenceState createFromParcel(Parcel in) {
+ return new ImsConferenceState(in);
+ }
+
+ @Override
+ public ImsConferenceState[] newArray(int size) {
+ return new ImsConferenceState[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/ims/ImsReasonInfo.aidl b/telephony/java/com/android/ims/ImsReasonInfo.aidl
new file mode 100644
index 0000000..17e6d3a
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsReasonInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims;
+
+parcelable ImsReasonInfo;
diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/com/android/ims/ImsReasonInfo.java
new file mode 100644
index 0000000..99faba6
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsReasonInfo.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class enables an application to get details on why a method call failed.
+ *
+ * @hide
+ */
+public class ImsReasonInfo implements Parcelable {
+
+ /**
+ * Reason types, defines the error category.
+ * UNSPECIFIED - unknown error reason
+ * LOCAL - indicates the local/device error reason
+ * LOCAL_TIMEOUT - indicates the local error reason when a specific timer is expired
+ * STATUSCODE - indicates the interworking error reason by SIP status code received
+ * from the network
+ * MEDIA - indicates the media error reason (local resource, SDP parameter, etc.)
+ * USER - indicates the error reason by the local or remote user
+ * UT - indicates the error reason for the supplementary service configuration
+ */
+ public static final int TYPE_UNSPECIFIED = 0;
+ public static final int TYPE_LOCAL = 1;
+ public static final int TYPE_TIMEOUT = 2;
+ public static final int TYPE_STATUSCODE = 3;
+ public static final int TYPE_MEDIA = 4;
+ public static final int TYPE_USER = 5;
+ public static final int TYPE_UT = 8;
+
+ /**
+ * Specific code of each types
+ */
+ public static final int CODE_UNSPECIFIED = 0;
+
+ /**
+ * LOCAL
+ */
+ // IMS -> Telephony
+ // The passed argument is an invalid
+ public static final int CODE_LOCAL_ILLEGAL_ARGUMENT = 101;
+ // The operation is invoked in invalid call state
+ public static final int CODE_LOCAL_ILLEGAL_STATE = 102;
+ // IMS service internal error
+ public static final int CODE_LOCAL_INTERNAL_ERROR = 103;
+ // IMS service goes down (service connection is lost)
+ public static final int CODE_LOCAL_IMS_SERVICE_DOWN = 106;
+ // No pending incoming call exists
+ public static final int CODE_LOCAL_NO_PENDING_CALL = 107;
+
+ // IMS -> Telephony
+ // Service unavailable; by power off
+ public static final int CODE_LOCAL_POWER_OFF = 111;
+ // Service unavailable; by low battery
+ public static final int CODE_LOCAL_LOW_BATTERY = 112;
+ // Service unavailable; by out of service (data service state)
+ public static final int CODE_LOCAL_NETWORK_NO_SERVICE = 121;
+ // Service unavailable; by no LTE coverage
+ // (VoLTE is not supported even though IMS is registered)
+ public static final int CODE_LOCAL_NETWORK_NO_LTE_COVERAGE = 122;
+ // Service unavailable; by located in roaming area
+ public static final int CODE_LOCAL_NETWORK_ROAMING = 123;
+ // Service unavailable; by IP changed
+ public static final int CODE_LOCAL_NETWORK_IP_CHANGED = 124;
+ // Service unavailable; other
+ public static final int CODE_LOCAL_SERVICE_UNAVAILABLE = 131;
+ // Service unavailable; IMS connection is lost (IMS is not registered)
+ public static final int CODE_LOCAL_NOT_REGISTERED = 132;
+
+ // IMS <-> Telephony
+ // Max call exceeded
+ public static final int CODE_LOCAL_CALL_EXCEEDED = 141;
+ // IMS <- Telephony
+ // Call busy
+ public static final int CODE_LOCAL_CALL_BUSY = 142;
+ // Call decline
+ public static final int CODE_LOCAL_CALL_DECLINE = 143;
+ // IMS -> Telephony
+ // SRVCC is in progress
+ public static final int CODE_LOCAL_CALL_VCC_ON_PROGRESSING = 144;
+ // Resource reservation is failed (QoS precondition)
+ public static final int CODE_LOCAL_CALL_RESOURCE_RESERVATION_FAILED = 145;
+ // Retry CS call; VoLTE service can't be provided by the network or remote end
+ // Resolve the extra code(EXTRA_CODE_CALL_RETRY_*) if the below code is set
+ public static final int CODE_LOCAL_CALL_CS_RETRY_REQUIRED = 146;
+ // Retry VoLTE call; VoLTE service can't be provided by the network temporarily
+ public static final int CODE_LOCAL_CALL_VOLTE_RETRY_REQUIRED = 147;
+ // IMS call is already terminated (in TERMINATED state)
+ public static final int CODE_LOCAL_CALL_TERMINATED = 148;
+
+ /**
+ * TIMEOUT (IMS -> Telephony)
+ */
+ // 1xx waiting timer is expired after sending INVITE request (MO only)
+ public static final int CODE_TIMEOUT_1XX_WAITING = 201;
+ // User no answer during call setup operation (MO/MT)
+ // MO : 200 OK to INVITE request is not received,
+ // MT : No action from user after alerting the call
+ public static final int CODE_TIMEOUT_NO_ANSWER = 202;
+ // User no answer during call update operation (MO/MT)
+ // MO : 200 OK to re-INVITE request is not received,
+ // MT : No action from user after alerting the call
+ public static final int CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE = 203;
+
+ /**
+ * STATUSCODE (SIP response code) (IMS -> Telephony)
+ */
+ // 3xx responses
+ // SIP request is redirected
+ public static final int CODE_SIP_REDIRECTED = 321;
+ // 4xx responses
+ // 400 : Bad Request
+ public static final int CODE_SIP_BAD_REQUEST = 331;
+ // 403 : Forbidden
+ public static final int CODE_SIP_FORBIDDEN = 332;
+ // 404 : Not Found
+ public static final int CODE_SIP_NOT_FOUND = 333;
+ // 415 : Unsupported Media Type
+ // 416 : Unsupported URI Scheme
+ // 420 : Bad Extension
+ public static final int CODE_SIP_NOT_SUPPORTED = 334;
+ // 408 : Request Timeout
+ public static final int CODE_SIP_REQUEST_TIMEOUT = 335;
+ // 480 : Temporarily Unavailable
+ public static final int CODE_SIP_TEMPRARILY_UNAVAILABLE = 336;
+ // 484 : Address Incomplete
+ public static final int CODE_SIP_BAD_ADDRESS = 337;
+ // 486 : Busy Here
+ // 600 : Busy Everywhere
+ public static final int CODE_SIP_BUSY = 338;
+ // 487 : Request Terminated
+ public static final int CODE_SIP_REQUEST_CANCELLED = 339;
+ // 406 : Not Acceptable
+ // 488 : Not Acceptable Here
+ // 606 : Not Acceptable
+ public static final int CODE_SIP_NOT_ACCEPTABLE = 340;
+ // 410 : Gone
+ // 604 : Does Not Exist Anywhere
+ public static final int CODE_SIP_NOT_REACHABLE = 341;
+ // Others
+ public static final int CODE_SIP_CLIENT_ERROR = 342;
+ // 5xx responses
+ // 501 : Server Internal Error
+ public static final int CODE_SIP_SERVER_INTERNAL_ERROR = 351;
+ // 503 : Service Unavailable
+ public static final int CODE_SIP_SERVICE_UNAVAILABLE = 352;
+ // 504 : Server Time-out
+ public static final int CODE_SIP_SERVER_TIMEOUT = 353;
+ // Others
+ public static final int CODE_SIP_SERVER_ERROR = 354;
+ // 6xx responses
+ // 603 : Decline
+ public static final int CODE_SIP_USER_REJECTED = 361;
+ // Others
+ public static final int CODE_SIP_GLOBAL_ERROR = 362;
+
+ /**
+ * MEDIA (IMS -> Telephony)
+ */
+ // Media resource initialization failed
+ public static final int CODE_MEDIA_INIT_FAILED = 401;
+ // RTP timeout (no audio / video traffic in the session)
+ public static final int CODE_MEDIA_NO_DATA = 402;
+ // Media is not supported; so dropped the call
+ public static final int CODE_MEDIA_NOT_ACCEPTABLE = 403;
+ // Unknown media related errors
+ public static final int CODE_MEDIA_UNSPECIFIED = 404;
+
+ /**
+ * USER
+ */
+ // Telephony -> IMS
+ // User triggers the call end
+ public static final int CODE_USER_TERMINATED = 501;
+ // No action while an incoming call is ringing
+ public static final int CODE_USER_NOANSWER = 502;
+ // User ignores an incoming call
+ public static final int CODE_USER_IGNORE = 503;
+ // User declines an incoming call
+ public static final int CODE_USER_DECLINE = 504;
+ // IMS -> Telephony
+ // The call is terminated by the network or remote user
+ public static final int CODE_USER_TERMINATED_BY_REMOTE = 510;
+
+ /**
+ * Extra codes for the specific code value
+ * This value can be referred when the code is CODE_LOCAL_CALL_CS_RETRY_REQUIRED.
+ */
+ // Try to connect CS call; normal
+ public static final int EXTRA_CODE_CALL_RETRY_NORMAL = 1;
+ // Try to connect CS call without the notification to user
+ public static final int EXTRA_CODE_CALL_RETRY_SILENT_REDIAL = 2;
+ // Try to connect CS call by the settings of the menu
+ public static final int EXTRA_CODE_CALL_RETRY_BY_SETTINGS = 3;
+
+ /**
+ * UT
+ */
+ public static final int CODE_UT_NOT_SUPPORTED = 801;
+ public static final int CODE_UT_SERVICE_UNAVAILABLE = 802;
+ public static final int CODE_UT_OPERATION_NOT_ALLOWED = 803;
+ public static final int CODE_UT_CB_PASSWORD_MISMATCH = 821;
+
+
+
+ // For reason type
+ public int mReasonType;
+ // For main reason code
+ public int mCode;
+ // For the extra code value; it depends on the code value.
+ public int mExtraCode;
+ // For the additional message of the reason info.
+ public String mExtraMessage;
+
+ public ImsReasonInfo() {
+ mReasonType = TYPE_UNSPECIFIED;
+ mCode = CODE_UNSPECIFIED;
+ mExtraCode = CODE_UNSPECIFIED;
+ mExtraMessage = null;
+ }
+
+ public ImsReasonInfo(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public ImsReasonInfo(int code, int extraCode) {
+ mReasonType = (int) (code / 100);
+ mCode = code;
+ mExtraCode = extraCode;
+ mExtraMessage = null;
+ }
+
+ public ImsReasonInfo(int code, int extraCode, String extraMessage) {
+ mReasonType = (int) (code / 100);
+ mCode = code;
+ mExtraCode = extraCode;
+ mExtraMessage = extraMessage;
+ }
+
+ /**
+ *
+ */
+ public int getCode() {
+ return mCode;
+ }
+
+ /**
+ *
+ */
+ public int getExtraCode() {
+ return mExtraCode;
+ }
+
+ /**
+ *
+ */
+ public String getExtraMessage() {
+ return mExtraMessage;
+ }
+
+ /**
+ *
+ */
+ public int getReasonType() {
+ return mReasonType;
+ }
+
+ /**
+ * Returns the string format of {@link ImsReasonInfo}
+ *
+ * @return the string format of {@link ImsReasonInfo}
+ */
+ public String toString() {
+ return "ImsReasonInfo :: {" + mReasonType + ", "
+ + mCode + ", " + mExtraCode + ", " + mExtraMessage + "}";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mReasonType);
+ out.writeInt(mCode);
+ out.writeInt(mExtraCode);
+ out.writeString(mExtraMessage);
+ }
+
+ private void readFromParcel(Parcel in) {
+ mReasonType = in.readInt();
+ mCode = in.readInt();
+ mExtraCode = in.readInt();
+ mExtraMessage = in.readString();
+ }
+
+ public static final Creator<ImsReasonInfo> CREATOR = new Creator<ImsReasonInfo>() {
+ @Override
+ public ImsReasonInfo createFromParcel(Parcel in) {
+ return new ImsReasonInfo(in);
+ }
+
+ @Override
+ public ImsReasonInfo[] newArray(int size) {
+ return new ImsReasonInfo[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/ims/ImsSsInfo.aidl b/telephony/java/com/android/ims/ImsSsInfo.aidl
new file mode 100644
index 0000000..0ac598b
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsSsInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims;
+
+parcelable ImsSsInfo;
diff --git a/telephony/java/com/android/ims/ImsSsInfo.java b/telephony/java/com/android/ims/ImsSsInfo.java
new file mode 100644
index 0000000..dbde1c6
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsSsInfo.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Provides the result to the update operation for the supplementary service configuration.
+ *
+ * @hide
+ */
+public class ImsSsInfo implements Parcelable {
+ /**
+ * For the status of service registration or activation/deactivation.
+ */
+ public static final int NOT_REGISTERED = (-1);
+ public static final int DISABLED = 0;
+ public static final int ENABLED = 1;
+
+ // 0: disabled, 1: enabled
+ public int mStatus;
+
+ public ImsSsInfo() {
+ }
+
+ public ImsSsInfo(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mStatus);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ", Status: " + ((mStatus == 0) ? "disabled" : "enabled");
+ }
+
+ private void readFromParcel(Parcel in) {
+ mStatus = in.readInt();
+ }
+
+ public static final Creator<ImsSsInfo> CREATOR =
+ new Creator<ImsSsInfo>() {
+ @Override
+ public ImsSsInfo createFromParcel(Parcel in) {
+ return new ImsSsInfo(in);
+ }
+
+ @Override
+ public ImsSsInfo[] newArray(int size) {
+ return new ImsSsInfo[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl b/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl
new file mode 100644
index 0000000..d648a35
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims;
+
+parcelable ImsStreamMediaProfile;
diff --git a/telephony/java/com/android/ims/ImsStreamMediaProfile.java b/telephony/java/com/android/ims/ImsStreamMediaProfile.java
new file mode 100644
index 0000000..003499c
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsStreamMediaProfile.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Parcelable object to handle IMS stream media profile.
+ * It provides the media direction, quality of audio and/or video.
+ *
+ * @hide
+ */
+public class ImsStreamMediaProfile implements Parcelable {
+ private static final String TAG = "ImsStreamMediaProfile";
+
+ /**
+ * Media directions
+ */
+ public static final int DIRECTION_INVALID = (-1);
+ public static final int DIRECTION_INACTIVE = 0;
+ public static final int DIRECTION_RECEIVE = 1;
+ public static final int DIRECTION_SEND = 2;
+ public static final int DIRECTION_SEND_RECEIVE = 3;
+
+ /**
+ * Audio information
+ */
+ public static final int AUDIO_QUALITY_NONE = 0;
+ public static final int AUDIO_QUALITY_AMR = (1 << 0);
+ public static final int AUDIO_QUALITY_AMR_WB = (1 << 1);
+
+ /**
+ * Video information
+ */
+ public static final int VIDEO_QUALITY_NONE = 0;
+ public static final int VIDEO_QUALITY_QCIF = (1 << 0);
+ public static final int VIDEO_QUALITY_QVGA_LANDSCAPE = (1 << 1);
+ public static final int VIDEO_QUALITY_QVGA_PORTRAIT = (1 << 2);
+ public static final int VIDEO_QUALITY_VGA_LANDSCAPE = (1 << 3);
+ public static final int VIDEO_QUALITY_VGA_PORTRAIT = (1 << 4);
+
+ // Audio related information
+ public int mAudioQuality;
+ public int mAudioDirection;
+ // Video related information
+ public int mVideoQuality;
+ public int mVideoDirection;
+
+
+
+ public ImsStreamMediaProfile(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public ImsStreamMediaProfile() {
+ mAudioQuality = AUDIO_QUALITY_AMR_WB;
+ mAudioDirection = DIRECTION_SEND_RECEIVE;
+ mVideoQuality = VIDEO_QUALITY_NONE;
+ mVideoDirection = DIRECTION_INVALID;
+ }
+
+ public ImsStreamMediaProfile(int audioQuality, int audioDirection,
+ int videoQuality, int videoDirection) {
+ mAudioQuality = audioQuality;
+ mAudioDirection = audioDirection;
+ mVideoQuality = videoQuality;
+ mVideoDirection = videoDirection;
+ }
+
+ public void copyFrom(ImsStreamMediaProfile profile) {
+ mAudioQuality = profile.mAudioQuality;
+ mAudioDirection = profile.mAudioDirection;
+ mVideoQuality = profile.mVideoQuality;
+ mVideoDirection = profile.mVideoDirection;
+ }
+
+ @Override
+ public String toString() {
+ return "{ audioQuality=" + mAudioQuality +
+ ", audioDirection=" + mAudioDirection +
+ ", videoQuality=" + mVideoQuality +
+ ", videoDirection=" + mVideoDirection + " }";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mAudioQuality);
+ out.writeInt(mAudioDirection);
+ out.writeInt(mVideoQuality);
+ out.writeInt(mVideoDirection);
+ }
+
+ private void readFromParcel(Parcel in) {
+ mAudioQuality = in.readInt();
+ mAudioDirection = in.readInt();
+ mVideoQuality = in.readInt();
+ mVideoDirection = in.readInt();
+ }
+
+ public static final Creator<ImsStreamMediaProfile> CREATOR =
+ new Creator<ImsStreamMediaProfile>() {
+ @Override
+ public ImsStreamMediaProfile createFromParcel(Parcel in) {
+ return new ImsStreamMediaProfile(in);
+ }
+
+ @Override
+ public ImsStreamMediaProfile[] newArray(int size) {
+ return new ImsStreamMediaProfile[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/ims/internal/IImsCallSession.aidl b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
new file mode 100644
index 0000000..5f2ff36
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims.internal;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.ImsStreamMediaProfile;
+import com.android.ims.internal.IImsCallSessionListener;
+
+/**
+ * An IMS session that is associated with a SIP dialog which is established from/to
+ * INVITE request or a mid-call transaction to control the session.
+ * {@hide}
+ */
+interface IImsCallSession {
+ /**
+ * Closes the object. This object is not usable after being closed.
+ */
+ void close();
+
+ /**
+ * Gets the call ID of the session.
+ *
+ * @return the call ID
+ */
+ String getCallId();
+
+ /**
+ * Gets the call profile that this session is associated with
+ *
+ * @return the call profile that this session is associated with
+ */
+ ImsCallProfile getCallProfile();
+
+ /**
+ * Gets the local call profile that this session is associated with
+ *
+ * @return the local call profile that this session is associated with
+ */
+ ImsCallProfile getLocalCallProfile();
+
+ /**
+ * Gets the value associated with the specified property of this session.
+ *
+ * @return the string value associated with the specified property
+ */
+ String getProperty(String name);
+
+ /**
+ * Gets the session state. The value returned must be one of the states in
+ * {@link ImsCallSession#State}.
+ *
+ * @return the session state
+ */
+ int getState();
+
+ /**
+ * Checks if the session is in a call.
+ *
+ * @return true if the session is in a call
+ */
+ boolean isInCall();
+
+ /**
+ * Sets the listener to listen to the session events. A {@link IImsCallSession}
+ * can only hold one listener at a time. Subsequent calls to this method
+ * override the previous listener.
+ *
+ * @param listener to listen to the session events of this object
+ */
+ void setListener(in IImsCallSessionListener listener);
+
+ /**
+ * Mutes or unmutes the mic for the active call.
+ *
+ * @param muted true if the call is muted, false otherwise
+ */
+ void setMute(boolean muted);
+
+ /**
+ * Initiates an IMS call with the specified target and call profile.
+ * The session listener is called back upon defined session events.
+ * The method is only valid to call when the session state is in
+ * {@link ImsCallSession#State#IDLE}.
+ *
+ * @param callee dialed string to make the call to
+ * @param profile call profile to make the call with the specified service type,
+ * call type and media information
+ * @see Listener#callSessionStarted, Listener#callSessionStartFailed
+ */
+ void start(String callee, in ImsCallProfile profile);
+
+ /**
+ * Initiates an IMS call with the specified participants and call profile.
+ * The session listener is called back upon defined session events.
+ * The method is only valid to call when the session state is in
+ * {@link ImsCallSession#State#IDLE}.
+ *
+ * @param participants participant list to initiate an IMS conference call
+ * @param profile call profile to make the call with the specified service type,
+ * call type and media information
+ * @see Listener#callSessionStarted, Listener#callSessionStartFailed
+ */
+ void startConference(in String[] participants, in ImsCallProfile profile);
+
+ /**
+ * Accepts an incoming call or session update.
+ *
+ * @param callType call type specified in {@link ImsCallProfile} to be answered
+ * @param profile stream media profile {@link ImsStreamMediaProfile} to be answered
+ * @see Listener#callSessionStarted
+ */
+ void accept(int callType, in ImsStreamMediaProfile profile);
+
+ /**
+ * Rejects an incoming call or session update.
+ *
+ * @param reason reason code to reject an incoming call
+ * @see Listener#callSessionStartFailed
+ */
+ void reject(int reason);
+
+ /**
+ * Terminates a call.
+ *
+ * @see Listener#callSessionTerminated
+ */
+ void terminate(int reason);
+
+ /**
+ * Puts a call on hold. When it succeeds, {@link Listener#callSessionHeld} is called.
+ *
+ * @param profile stream media profile {@link ImsStreamMediaProfile} to hold the call
+ * @see Listener#callSessionHeld, Listener#callSessionHoldFailed
+ */
+ void hold(in ImsStreamMediaProfile profile);
+
+ /**
+ * Continues a call that's on hold. When it succeeds, {@link Listener#callSessionResumed}
+ * is called.
+ *
+ * @param profile stream media profile {@link ImsStreamMediaProfile} to resume the call
+ * @see Listener#callSessionResumed, Listener#callSessionResumeFailed
+ */
+ void resume(in ImsStreamMediaProfile profile);
+
+ /**
+ * Merges the active & hold call. When it succeeds, {@link Listener#callSessionMerged}
+ * is called.
+ *
+ * @see Listener#callSessionMerged, Listener#callSessionMergeFailed
+ */
+ void merge();
+
+ /**
+ * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
+ *
+ * @param callType call type specified in {@link ImsCallProfile} to be updated
+ * @param profile stream media profile {@link ImsStreamMediaProfile} to be updated
+ * @see Listener#callSessionUpdated, Listener#callSessionUpdateFailed
+ */
+ void update(int callType, in ImsStreamMediaProfile profile);
+
+ /**
+ * Extends this call to the conference call with the specified recipients.
+ *
+ * @param participants participant list to be invited to the conference call after extending the call
+ * @see Listener#sessionConferenceExtened, Listener#sessionConferenceExtendFailed
+ */
+ void extendToConference(in String[] participants);
+
+ /**
+ * Requests the conference server to invite an additional participants to the conference.
+ *
+ * @param participants participant list to be invited to the conference call
+ * @see Listener#sessionInviteParticipantsRequestDelivered,
+ * Listener#sessionInviteParticipantsRequestFailed
+ */
+ void inviteParticipants(in String[] participants);
+
+ /**
+ * Requests the conference server to remove the specified participants from the conference.
+ *
+ * @param participants participant list to be removed from the conference call
+ * @see Listener#sessionRemoveParticipantsRequestDelivered,
+ * Listener#sessionRemoveParticipantsRequestFailed
+ */
+ void removeParticipants(in String[] participants);
+
+ /**
+ * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
+ * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
+ * and event flash to 16. Currently, event flash is not supported.
+ *
+ * @param code the DTMF to send. Value 0 to 15 (inclusive) are valid inputs.
+ * @param duration the interval in milli-seconds between the DTMFs
+ */
+ void sendDtmf(int code, int duration);
+
+ /**
+ * Sends an USSD message.
+ *
+ * @param ussdMessage USSD message to send
+ */
+ void sendUssd(String ussdMessage);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
new file mode 100644
index 0000000..f36cf39
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims.internal;
+
+import com.android.ims.ImsStreamMediaProfile;
+import com.android.ims.ImsCallProfile;
+import com.android.ims.ImsReasonInfo;
+import com.android.ims.ImsConferenceState;
+import com.android.ims.internal.IImsCallSession;
+
+/**
+ * A listener type for receiving notification on IMS call session events.
+ * When an event is generated for an {@link IImsCallSession}, the application is notified
+ * by having one of the methods called on the {@link IImsCallSessionListener}.
+ * {@hide}
+ */
+interface IImsCallSessionListener {
+ /**
+ * Notifies the result of the basic session operation (setup / terminate).
+ */
+ void callSessionProgressing(in IImsCallSession session, in ImsStreamMediaProfile profile);
+ void callSessionStarted(in IImsCallSession session, in ImsCallProfile profile);
+ void callSessionStartFailed(in IImsCallSession session, in ImsReasonInfo reasonInfo);
+ void callSessionTerminated(in IImsCallSession session, in ImsReasonInfo reasonInfo);
+
+ /**
+ * Notifies the result of the call hold/resume operation.
+ */
+ void callSessionHeld(in IImsCallSession session, in ImsCallProfile profile);
+ void callSessionHoldFailed(in IImsCallSession session, in ImsReasonInfo reasonInfo);
+ void callSessionHoldReceived(in IImsCallSession session, in ImsCallProfile profile);
+ void callSessionResumed(in IImsCallSession session, in ImsCallProfile profile);
+ void callSessionResumeFailed(in IImsCallSession session, in ImsReasonInfo reasonInfo);
+ void callSessionResumeReceived(in IImsCallSession session, in ImsCallProfile profile);
+
+ /**
+ * Notifiies the result of call merge operation.
+ */
+ void callSessionMerged(in IImsCallSession session,
+ in IImsCallSession newSession, in ImsCallProfile profile);
+ void callSessionMergeFailed(in IImsCallSession session,
+ in ImsReasonInfo reasonInfo);
+
+ /**
+ * Notifies the result of call upgrade / downgrade or any other call updates.
+ */
+ void callSessionUpdated(in IImsCallSession session,
+ in ImsCallProfile profile);
+ void callSessionUpdateFailed(in IImsCallSession session,
+ in ImsReasonInfo reasonInfo);
+ void callSessionUpdateReceived(in IImsCallSession session,
+ in ImsCallProfile profile);
+
+ /**
+ * Notifies the result of conference extension.
+ */
+ void callSessionConferenceExtended(in IImsCallSession session,
+ in IImsCallSession newSession, in ImsCallProfile profile);
+ void callSessionConferenceExtendFailed(in IImsCallSession session,
+ in ImsReasonInfo reasonInfo);
+ void callSessionConferenceExtendReceived(in IImsCallSession session,
+ in IImsCallSession newSession, in ImsCallProfile profile);
+
+ /**
+ * Notifies the result of the participant invitation / removal to/from the conference session.
+ */
+ void callSessionInviteParticipantsRequestDelivered(in IImsCallSession session);
+ void callSessionInviteParticipantsRequestFailed(in IImsCallSession session,
+ in ImsReasonInfo reasonInfo);
+ void callSessionRemoveParticipantsRequestDelivered(in IImsCallSession session);
+ void callSessionRemoveParticipantsRequestFailed(in IImsCallSession session,
+ in ImsReasonInfo reasonInfo);
+
+ /**
+ * Notifies the changes of the conference info. in the conference session.
+ */
+ void callSessionConferenceStateUpdated(in IImsCallSession session,
+ in ImsConferenceState state);
+
+ /**
+ * Notifies the incoming USSD message.
+ */
+ void callSessionUssdMessageReceived(in IImsCallSession session,
+ int mode, String ussdMessage);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl b/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl
new file mode 100644
index 0000000..5f243a0
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims.internal;
+
+/**
+ * A listener type for receiving notifications about the changes to
+ * the IMS connection(registration).
+ *
+ * {@hide}
+ */
+interface IImsRegistrationListener {
+ /**
+ * Notifies the application when the device is connected to the IMS network.
+ */
+ void registrationConnected();
+
+ /**
+ * Notifies the application when the device is disconnected from the IMS network.
+ */
+ void registrationDisconnected();
+
+ /**
+ * Notifies the application when its suspended IMS connection is resumed,
+ * meaning the connection now allows throughput.
+ */
+ void registrationResumed();
+
+ /**
+ * Notifies the application when its current IMS connection is suspended,
+ * meaning there is no data throughput.
+ */
+ void registrationSuspended();
+
+ /**
+ * Notifies the application when its current IMS connection is updated
+ * since the service setting is changed or the service is added/removed.
+ *
+ * @param serviceClass a service class specified in {@link ImsServiceClass}
+ * @param event an event type when this callback is called
+ * If {@code event} is 0, meaning the specified service is removed from the IMS connection.
+ * Else ({@code event} is 1), meaning the specified service is added to the IMS connection.
+ */
+ void registrationServiceCapabilityChanged(int serviceClass, int event);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsService.aidl b/telephony/java/com/android/ims/internal/IImsService.aidl
new file mode 100644
index 0000000..bac5651
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsService.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims.internal;
+
+import android.app.PendingIntent;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsCallSessionListener;
+import com.android.ims.internal.IImsUt;
+
+/**
+ * {@hide}
+ */
+interface IImsService {
+ int open(int serviceClass, in PendingIntent incomingCallIntent,
+ in IImsRegistrationListener listener);
+ void close(int serviceId);
+ boolean isConnected(int serviceId, int serviceType, int callType);
+ boolean isOpened(int serviceId);
+ void setRegistrationListener(int serviceId, in IImsRegistrationListener listener);
+
+ ImsCallProfile createCallProfile(int serviceId, int serviceType, int callType);
+
+ IImsCallSession createCallSession(int serviceId, in ImsCallProfile profile,
+ in IImsCallSessionListener listener);
+ IImsCallSession getPendingCallSession(int serviceId, String callId);
+
+ /**
+ * Ut interface for the supplementary service configuration.
+ */
+ IImsUt getUtInterface(int serviceId);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl b/telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl
new file mode 100644
index 0000000..459c685
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims.internal;
+
+/**
+ *
+ * {@hide}
+ */
+interface IImsStreamMediaSession {
+ void close();
+}
diff --git a/telephony/java/com/android/ims/internal/IImsUt.aidl b/telephony/java/com/android/ims/internal/IImsUt.aidl
new file mode 100644
index 0000000..32929ed
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsUt.aidl
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims.internal;
+
+import android.os.Bundle;
+
+import com.android.ims.internal.IImsUtListener;
+
+/**
+ * Provides the Ut interface interworking to get/set the supplementary service configuration.
+ *
+ * {@hide}
+ */
+interface IImsUt {
+ /**
+ * Closes the object. This object is not usable after being closed.
+ */
+ void close();
+
+ /**
+ * Retrieves the configuration of the call barring.
+ */
+ int queryCallBarring(int cbType);
+
+ /**
+ * Retrieves the configuration of the call forward.
+ */
+ int queryCallForward(int condition, String number);
+
+ /**
+ * Retrieves the configuration of the call waiting.
+ */
+ int queryCallWaiting();
+
+ /**
+ * Updates or retrieves the supplementary service configuration.
+ */
+ int transact(in Bundle ssInfo);
+
+ /**
+ * Updates the configuration of the call barring.
+ */
+ int updateCallBarring(int cbType, boolean enable);
+
+ /**
+ * Updates the configuration of the call forward.
+ */
+ int updateCallForward(int action, int condition, String number, int timeSeconds);
+
+ /**
+ * Updates the configuration of the call waiting.
+ */
+ int updateCallWaiting(boolean enable);
+
+ /**
+ * Sets the listener.
+ */
+ void setListener(in IImsUtListener listener);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsUtListener.aidl b/telephony/java/com/android/ims/internal/IImsUtListener.aidl
new file mode 100644
index 0000000..3f1b5a7
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsUtListener.aidl
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.ims.internal;
+
+import android.os.Bundle;
+
+import com.android.ims.ImsCallForwardInfo;
+import com.android.ims.ImsSsInfo;
+import com.android.ims.internal.IImsUt;
+
+/**
+ * {@hide}
+ */
+interface IImsUtListener {
+ /**
+ * Notifies the result of the supplementary service configuration udpate.
+ */
+ void utConfigurationUpdated(in IImsUt ut, int id);
+ void utConfigurationUpdateFailed(in IImsUt ut, int id, int errorCode);
+
+ /**
+ * Notifies the result of the supplementary service configuration query.
+ */
+ void utConfigurationQueried(in IImsUt ut, int id, in Bundle ssInfo);
+ void utConfigurationQueryFailed(in IImsUt ut, int id, int errorCode);
+
+ /**
+ * Notifies the status of the call barring supplementary service.
+ */
+ void utConfigurationCallBarringQueried(in IImsUt ut,
+ int id, in ImsSsInfo[] cbInfo);
+
+ /**
+ * Notifies the status of the call forwarding supplementary service.
+ */
+ void utConfigurationCallForwardQueried(in IImsUt ut,
+ int id, in ImsCallForwardInfo[] cfInfo);
+
+ /**
+ * Notifies the status of the call waiting supplementary service.
+ */
+ void utConfigurationCallWaitingQueried(in IImsUt ut,
+ int id, in ImsSsInfo[] cwInfo);
+}
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 1d10729..59036da 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -305,6 +305,12 @@
throw new UnsupportedOperationException();
}
+ /** @hide */
+ @Override
+ public ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) {
throw new UnsupportedOperationException();
diff --git a/tests/VectorDrawableTest/res/anim/animation_favorite.xml b/tests/VectorDrawableTest/res/anim/animation_favorite.xml
new file mode 100644
index 0000000..c81ba40
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/animation_favorite.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <objectAnimator
+ android:duration="8000"
+ android:interpolator="@interpolator/custom_path_interpolator_favorite"
+ android:propertyName="pathData"
+ android:repeatCount="-1"
+ android:valueFrom="@string/round_box"
+ android:valueTo="@string/heart"
+ android:valueType="pathType" />
+
+</set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/anim/animation_favorite02.xml b/tests/VectorDrawableTest/res/anim/animation_favorite02.xml
new file mode 100644
index 0000000..15d3b8e
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/animation_favorite02.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <objectAnimator
+ android:duration="8000"
+ android:interpolator="@interpolator/custom_path_interpolator_favorite"
+ android:propertyName="scaleX"
+ android:repeatCount="-1"
+ android:valueFrom="0"
+ android:valueTo="20" />
+ <objectAnimator
+ android:duration="8000"
+ android:interpolator="@interpolator/custom_path_interpolator_favorite"
+ android:propertyName="scaleY"
+ android:repeatCount="-1"
+ android:valueFrom="0"
+ android:valueTo="20" />
+
+</set>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_status_bar_scrim.xml b/tests/VectorDrawableTest/res/anim/animation_grouping_1_01.xml
similarity index 63%
copy from packages/SystemUI/res/layout/recents_status_bar_scrim.xml
copy to tests/VectorDrawableTest/res/anim/animation_grouping_1_01.xml
index 24928d0..8d82d05 100644
--- a/packages/SystemUI/res/layout/recents_status_bar_scrim.xml
+++ b/tests/VectorDrawableTest/res/anim/animation_grouping_1_01.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+ 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.
@@ -14,10 +15,12 @@
limitations under the License.
-->
-<ImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal|top"
- android:scaleType="fitXY"
- android:src="@drawable/recents_status_gradient" />
\ No newline at end of file
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <objectAnimator
+ android:duration="3300"
+ android:propertyName="rotation"
+ android:valueFrom="0"
+ android:valueTo="360"
+ android:repeatCount="-1" />
+</set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/anim/animation_grouping_1_02.xml b/tests/VectorDrawableTest/res/anim/animation_grouping_1_02.xml
new file mode 100644
index 0000000..ae63203
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/animation_grouping_1_02.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <objectAnimator
+ android:duration="2000"
+ android:interpolator="@interpolator/custom_path_interpolator_favorite"
+ android:propertyName="scaleX"
+ android:repeatCount="-1"
+ android:valueFrom="0"
+ android:valueTo="20" />
+ <objectAnimator
+ android:duration="2000"
+ android:interpolator="@interpolator/custom_path_interpolator_favorite"
+ android:propertyName="scaleY"
+ android:repeatCount="-1"
+ android:valueFrom="0"
+ android:valueTo="20" />
+
+</set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/anim/trim_path_animation05.xml b/tests/VectorDrawableTest/res/anim/trim_path_animation05.xml
index 7012f4b..92b1ab5 100644
--- a/tests/VectorDrawableTest/res/anim/trim_path_animation05.xml
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation05.xml
@@ -21,8 +21,8 @@
<objectAnimator
android:duration="3000"
android:propertyName="pathData"
- android:valueFrom="@string/triangle"
- android:valueTo="@string/rectangle"
+ android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z"
+ android:valueTo= "M300,70 l 0,-70 70,0 0,140 -70,0 z"
android:valueType="pathType"/>
<objectAnimator
diff --git a/tests/VectorDrawableTest/res/anim/trim_path_animation_progress_bar.xml b/tests/VectorDrawableTest/res/anim/trim_path_animation_progress_bar.xml
new file mode 100644
index 0000000..f510256
--- /dev/null
+++ b/tests/VectorDrawableTest/res/anim/trim_path_animation_progress_bar.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <objectAnimator
+ android:duration="1300"
+ android:interpolator="@interpolator/trim_start_interpolator"
+ android:propertyName="trimPathStart"
+ android:repeatCount="-1"
+ android:valueFrom="0"
+ android:valueTo="0.75"
+ android:valueType="floatType" />
+ <objectAnimator
+ android:duration="1300"
+ android:interpolator="@interpolator/trim_end_interpolator"
+ android:propertyName="trimPathEnd"
+ android:repeatCount="-1"
+ android:valueFrom="0"
+ android:valueTo="0.75"
+ android:valueType="floatType" />
+ <objectAnimator
+ android:duration="1300"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:propertyName="trimPathOffset"
+ android:repeatCount="-1"
+ android:valueFrom="0"
+ android:valueTo="1.25"
+ android:valueType="floatType" />
+
+</set>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml
index b37b19f..19b82ad 100644
--- a/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml
+++ b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable01.xml
@@ -19,14 +19,12 @@
<target
android:name="pie1"
android:animation="@anim/trim_path_animation01" />
-
<target
android:name="v"
android:animation="@anim/trim_path_animation02" />
<target
android:name="v"
android:animation="@anim/trim_path_animation05" />
-
<target
android:name="rotationGroup"
android:animation="@anim/trim_path_animation03" />
@@ -40,4 +38,4 @@
android:name="rotationGroup"
android:animation="@anim/trim_path_animation04" />
-</animated-vector>
+</animated-vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/animation_vector_drawable_favorite.xml b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable_favorite.xml
new file mode 100644
index 0000000..9d8381f
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable_favorite.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/vector_drawable_favorite" >
+
+ <target
+ android:name="favorite"
+ android:animation="@anim/animation_favorite" />
+ <target
+ android:name="root"
+ android:animation="@anim/animation_favorite02" />
+
+</animated-vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/animation_vector_drawable_grouping_1.xml b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable_grouping_1.xml
new file mode 100644
index 0000000..4a7e4f6
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/animation_vector_drawable_grouping_1.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/vector_drawable_grouping_1" >
+
+ <target
+ android:name="sun"
+ android:animation="@anim/animation_grouping_1_01" />
+ <target
+ android:name="earth"
+ android:animation="@anim/animation_grouping_1_01" />
+
+</animated-vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/animation_vector_progress_bar.xml b/tests/VectorDrawableTest/res/drawable/animation_vector_progress_bar.xml
new file mode 100644
index 0000000..6621e41
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/animation_vector_progress_bar.xml
@@ -0,0 +1,23 @@
+<!--
+ 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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/vector_drawable_progress_bar" >
+
+ <target
+ android:name="pie1"
+ android:animation="@anim/trim_path_animation_progress_bar" />
+
+</animated-vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable_favorite.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable_favorite.xml
new file mode 100644
index 0000000..c8840f5
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable_favorite.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="128dp"
+ android:width="128dp" />
+
+ <viewport
+ android:viewportHeight="480"
+ android:viewportWidth="480" />
+
+ <group
+ android:name="root"
+ android:translateX="240.0"
+ android:translateY="240.0" >
+ <path
+ android:name="favorite"
+ android:fill="#ff000000"
+ android:pathData="M2.100006104,-6
+ C0.1449127197,-6,1.600006104,-5.975006104,0,-5.975006104
+ C-1.574996948,-5.975006104,0.00309753418,-6,-1.949996948,-6
+ C-4.492996216,-6,-5.949996948,-3.718399048,-5.949996948,-1.149993896
+ C-5.949996948,2.379302979,-5.699996948,5.100006104,0,5.100006104
+ C5.699996948,5.100006104,6,2.379302979,6,-1.149993896
+ C6,-3.718399048,4.643005371,-6,2.100006104,-6" />
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable_grouping_1.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable_grouping_1.xml
new file mode 100644
index 0000000..558de94
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable_grouping_1.xml
@@ -0,0 +1,56 @@
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="256"
+ android:viewportWidth="256" />
+
+ <group
+ android:name="shape_layer_1"
+ android:translateX="128"
+ android:translateY="128" >
+ <group android:name="sun" >
+ <path
+ android:name="ellipse_path_1"
+ android:fill="#ffff8000"
+ android:pathData="m -25 0 a 25,25 0 1,0 50,0 a 25,25 0 1,0 -50,0" />
+
+ <group
+ android:name="earth"
+ android:translateX="75" >
+ <path
+ android:name="ellipse_path_1_1"
+ android:fill="#ff5656ea"
+ android:pathData="m -10 0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0" />
+
+ <group
+ android:name="moon"
+ android:translateX="25" >
+ <path
+ android:name="ellipse_path_1_2"
+ android:fill="#ffadadad"
+ android:pathData="m -5 0 a 5,5 0 1,0 10,0 a 5,5 0 1,0 -10,0" />
+ </group>
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/drawable/vector_drawable_progress_bar.xml b/tests/VectorDrawableTest/res/drawable/vector_drawable_progress_bar.xml
new file mode 100644
index 0000000..956c600
--- /dev/null
+++ b/tests/VectorDrawableTest/res/drawable/vector_drawable_progress_bar.xml
@@ -0,0 +1,52 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <size
+ android:height="64dp"
+ android:width="64dp" />
+
+ <viewport
+ android:viewportHeight="64"
+ android:viewportWidth="64" />
+
+ <group
+ android:name="root"
+ android:pivotX="0.0"
+ android:pivotY="0.0"
+ android:rotation="0"
+ android:translateX="32.0"
+ android:translateY="32.0" >
+ <group
+ android:name="rotationGroup"
+ android:pivotX="0.0"
+ android:pivotY="0.0"
+ android:rotation="0" >
+ <path
+ android:name="pie1"
+ android:fill="#00000000"
+ android:pathData="M0, 0 m 0, -9.5 a 9.5,9.5 0 1,1 0,19 a 9.5,9.5 0 1,1 0,-19"
+ android:stroke="#89cff0"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="miter"
+ android:strokeWidth="2"
+ android:trimPathEnd="0.1"
+ android:trimPathOffset="0"
+ android:trimPathStart="0" />
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator_favorite.xml b/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator_favorite.xml
new file mode 100644
index 0000000..935d5b5
--- /dev/null
+++ b/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator_favorite.xml
@@ -0,0 +1,2 @@
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="L0.1, 0 C0.10066,0 0.198,1 0.2, 1 L 1,1" />
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator_grouping_1_01.xml b/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator_grouping_1_01.xml
new file mode 100644
index 0000000..8c57395
--- /dev/null
+++ b/tests/VectorDrawableTest/res/interpolator/custom_path_interpolator_grouping_1_01.xml
@@ -0,0 +1,2 @@
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="L 0.09 1 L 1,1" />
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/interpolator/trim_end_interpolator.xml b/tests/VectorDrawableTest/res/interpolator/trim_end_interpolator.xml
new file mode 100644
index 0000000..b2770cd
--- /dev/null
+++ b/tests/VectorDrawableTest/res/interpolator/trim_end_interpolator.xml
@@ -0,0 +1,2 @@
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="C0.2,0 0.1,1 0.5, 1 L 1,1" />
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/interpolator/trim_start_interpolator.xml b/tests/VectorDrawableTest/res/interpolator/trim_start_interpolator.xml
new file mode 100644
index 0000000..798f7e6
--- /dev/null
+++ b/tests/VectorDrawableTest/res/interpolator/trim_start_interpolator.xml
@@ -0,0 +1,2 @@
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:pathData="L0.5,0 C 0.7,0 0.6,1 1, 1" />
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/res/values/strings.xml b/tests/VectorDrawableTest/res/values/strings.xml
index 6ae3d7f..54cffa8 100644
--- a/tests/VectorDrawableTest/res/values/strings.xml
+++ b/tests/VectorDrawableTest/res/values/strings.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+ 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.
@@ -15,12 +16,13 @@
-->
<resources>
- <string name="twoLinePathData" >"M 0,0 v 100 M 0,0 h 100"</string>
- <string name="triangle" > "M300,70 l 0,-70 70,70 0,0 -70,70z"</string>
- <string name="rectangle" >"M300,70 l 0,-70 70,0 0,140 -70,0 z"</string>
+ <string name="twoLinePathData">"M 0,0 v 100 M 0,0 h 100"</string>
+ <string name="triangle"> "M300,70 l 0,-70 70,70 0,0 -70,70z"</string>
+ <string name="rectangle">"M300,70 l 0,-70 70,0 0,140 -70,0 z"</string>
+ <string name="rectangle2">"M300,70 l 0,-70 70,0 0,70z M300,70 l 70,0 0,70 -70,0z"</string>
+ <string name="equal2"> "M300,35 l 0,-35 70,0 0,35z M300,105 l 70,0 0,35 -70,0z"</string>
+ <string name="round_box">"m2.10001,-6c-1.9551,0 -0.5,0.02499 -2.10001,0.02499c-1.575,0 0.0031,-0.02499 -1.95,-0.02499c-2.543,0 -4,2.2816 -4,4.85001c0,3.52929 0.25,6.25 5.95,6.25c5.7,0 6,-2.72071 6,-6.25c0,-2.56841 -1.35699,-4.85001 -3.89999,-4.85001"</string>
+ <string name="heart"> "m4.5,-7c-1.95509,0 -3.83009,1.26759 -4.5,3c-0.66991,-1.73241 -2.54691,-3 -4.5,-3c-2.543,0 -4.5,1.93159 -4.5,4.5c0,3.5293 3.793,6.2578 9,11.5c5.207,-5.2422 9,-7.9707 9,-11.5c0,-2.56841 -1.957,-4.5 -4.5,-4.5"</string>
- <string name="rectangle2" >"M300,70 l 0,-70 70,0 0,70z M300,70 l 70,0 0,70 -70,0z"</string>
- <string name="equal2" > "M300,35 l 0,-35 70,0 0,35z M300,105 l 70,0 0,35 -70,0z"</string>
-
-</resources>
+</resources>
\ No newline at end of file
diff --git a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java
index 6e864fa..b1ba0dd 100644
--- a/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java
+++ b/tests/VectorDrawableTest/src/com/android/test/dynamic/AnimatedVectorDrawableTest.java
@@ -19,24 +19,43 @@
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
+import android.widget.GridLayout;
+import android.widget.ScrollView;
-public class AnimatedVectorDrawableTest extends Activity {
- private static final String LOGCAT = "VectorDrawableAnimationTest";
+public class AnimatedVectorDrawableTest extends Activity implements View.OnClickListener{
+ private static final String LOGCAT = "AnimatedVectorDrawableTest";
+
+ protected int[] icon = {
+ R.drawable.animation_vector_drawable_grouping_1,
+ R.drawable.animation_vector_progress_bar,
+ R.drawable.animation_vector_drawable_favorite,
+ R.drawable.animation_vector_drawable01,
+ };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Button button = new Button(this);
- button.setBackgroundResource(R.drawable.animation_vector_drawable01);
- button.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- AnimatedVectorDrawable frameAnimation = (AnimatedVectorDrawable) v.getBackground();
- frameAnimation.start();
- }
- });
+ ScrollView scrollView = new ScrollView(this);
+ GridLayout container = new GridLayout(this);
+ scrollView.addView(container);
+ container.setColumnCount(1);
- setContentView(button);
+ for (int i = 0; i < icon.length; i++) {
+ Button button = new Button(this);
+ button.setWidth(400);
+ button.setHeight(400);
+ button.setBackgroundResource(icon[i]);
+ container.addView(button);
+ button.setOnClickListener(this);
+ }
+
+ setContentView(scrollView);
+ }
+
+ @Override
+ public void onClick(View v) {
+ AnimatedVectorDrawable d = (AnimatedVectorDrawable) v.getBackground();
+ d.start();
}
}
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index cc0da15..816033e 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -407,6 +407,8 @@
ICON_ATTR = 0x01010002,
NAME_ATTR = 0x01010003,
PERMISSION_ATTR = 0x01010006,
+ EXPORTED_ATTR = 0x01010010,
+ GRANT_URI_PERMISSIONS_ATTR = 0x0101001b,
RESOURCE_ATTR = 0x01010025,
DEBUGGABLE_ATTR = 0x0101000f,
VALUE_ATTR = 0x01010024,
@@ -582,6 +584,10 @@
return categories;
}
+static void printComponentPresence(const char* componentName) {
+ printf("provides-component:'%s'\n", componentName);
+}
+
/*
* Handle the "dump" command, to extract select data from an archive.
*/
@@ -793,19 +799,27 @@
bool withinSupportsInput = false;
bool withinReceiver = false;
bool withinService = false;
+ bool withinProvider = false;
bool withinIntentFilter = false;
bool hasMainActivity = false;
bool hasOtherActivities = false;
bool hasOtherReceivers = false;
bool hasOtherServices = false;
+ bool hasIntentFilter = false;
+
bool hasWallpaperService = false;
bool hasImeService = false;
bool hasAccessibilityService = false;
bool hasPrintService = false;
bool hasWidgetReceivers = false;
bool hasDeviceAdminReceiver = false;
- bool hasIntentFilter = false;
bool hasPaymentService = false;
+ bool hasDocumentsProvider = false;
+ bool hasCameraActivity = false;
+ bool hasCameraSecureActivity = false;
+ bool hasLauncher = false;
+ bool hasNotificationListenerService = false;
+
bool actMainActivity = false;
bool actWidgetReceivers = false;
bool actDeviceAdminEnabled = false;
@@ -815,6 +829,11 @@
bool actPrintService = false;
bool actHostApduService = false;
bool actOffHostApduService = false;
+ bool actDocumentsProvider = false;
+ bool actNotificationListenerService = false;
+ bool actCamera = false;
+ bool actCameraSecure = false;
+ bool catLauncher = false;
bool hasMetaHostPaymentCategory = false;
bool hasMetaOffHostPaymentCategory = false;
@@ -825,6 +844,8 @@
bool hasBindAccessibilityServicePermission = false;
bool hasBindPrintServicePermission = false;
bool hasBindNfcServicePermission = false;
+ bool hasRequiredSafAttributes = false;
+ bool hasBindNotificationListenerServicePermission = false;
// These two implement the implicit permissions that are granted
// to pre-1.6 applications.
@@ -962,13 +983,17 @@
withinActivity = false;
withinService = false;
withinReceiver = false;
+ withinProvider = false;
hasIntentFilter = false;
isMainActivity = isLauncherActivity = isLeanbackLauncherActivity = false;
} else if (depth < 4) {
if (withinIntentFilter) {
if (withinActivity) {
hasMainActivity |= actMainActivity;
- hasOtherActivities |= !actMainActivity;
+ hasLauncher |= catLauncher;
+ hasCameraActivity |= actCamera;
+ hasCameraSecureActivity |= actCameraSecure;
+ hasOtherActivities |= !actMainActivity && !actCamera && !actCameraSecure;
} else if (withinReceiver) {
hasWidgetReceivers |= actWidgetReceivers;
hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
@@ -980,9 +1005,14 @@
hasAccessibilityService |= (actAccessibilityService &&
hasBindAccessibilityServicePermission);
hasPrintService |= (actPrintService && hasBindPrintServicePermission);
+ hasNotificationListenerService |= actNotificationListenerService &&
+ hasBindNotificationListenerServicePermission;
hasOtherServices |= (!actImeService && !actWallpaperService &&
!actAccessibilityService && !actPrintService &&
- !actHostApduService && !actOffHostApduService);
+ !actHostApduService && !actOffHostApduService &&
+ !actNotificationListenerService);
+ } else if (withinProvider) {
+ hasDocumentsProvider |= actDocumentsProvider && hasRequiredSafAttributes;
}
}
withinIntentFilter = false;
@@ -1348,6 +1378,7 @@
withinActivity = false;
withinReceiver = false;
withinService = false;
+ withinProvider = false;
hasIntentFilter = false;
hasMetaHostPaymentCategory = false;
hasMetaOffHostPaymentCategory = false;
@@ -1356,6 +1387,8 @@
hasBindAccessibilityServicePermission = false;
hasBindPrintServicePermission = false;
hasBindNfcServicePermission = false;
+ hasRequiredSafAttributes = false;
+ hasBindNotificationListenerServicePermission = false;
if (withinApplication) {
if(tag == "activity") {
withinActivity = true;
@@ -1451,11 +1484,41 @@
hasBindPrintServicePermission = true;
} else if (permission == "android.permission.BIND_NFC_SERVICE") {
hasBindNfcServicePermission = true;
+ } else if (permission == "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") {
+ hasBindNotificationListenerServicePermission = true;
}
} else {
fprintf(stderr, "ERROR getting 'android:permission' attribute for"
" service '%s': %s\n", serviceName.string(), error.string());
}
+ } else if (tag == "provider") {
+ withinProvider = true;
+
+ bool exported = getResolvedIntegerAttribute(&res, tree, EXPORTED_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:exported' attribute for provider:"
+ " %s\n", error.string());
+ goto bail;
+ }
+
+ bool grantUriPermissions = getResolvedIntegerAttribute(&res, tree,
+ GRANT_URI_PERMISSIONS_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:grantUriPermissions' attribute for provider:"
+ " %s\n", error.string());
+ goto bail;
+ }
+
+ String8 permission = getResolvedAttribute(&res, tree, PERMISSION_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:permission' attribute for provider:"
+ " %s\n", error.string());
+ goto bail;
+ }
+
+ hasRequiredSafAttributes |= exported && grantUriPermissions &&
+ permission == "android.permission.MANAGE_DOCUMENTS";
+
} else if (bundle->getIncludeMetaData() && tag == "meta-data") {
String8 metaDataName = getResolvedAttribute(&res, tree, NAME_ATTR, &error);
if (error != "") {
@@ -1504,6 +1567,11 @@
actDeviceAdminEnabled = false;
actHostApduService = false;
actOffHostApduService = false;
+ actDocumentsProvider = false;
+ actNotificationListenerService = false;
+ actCamera = false;
+ actCameraSecure = false;
+ catLauncher = false;
} else if (withinService && tag == "meta-data") {
String8 name = getAttribute(tree, NAME_ATTR, &error);
if (error != "") {
@@ -1559,6 +1627,11 @@
if (action == "android.intent.action.MAIN") {
isMainActivity = true;
actMainActivity = true;
+ } else if (action == "android.media.action.STILL_IMAGE_CAMERA" ||
+ action == "android.media.action.VIDEO_CAMERA") {
+ actCamera = true;
+ } else if (action == "android.media.action.STILL_IMAGE_CAMERA_SECURE") {
+ actCameraSecure = true;
}
} else if (withinReceiver) {
if (action == "android.appwidget.action.APPWIDGET_UPDATE") {
@@ -1579,6 +1652,12 @@
actHostApduService = true;
} else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
actOffHostApduService = true;
+ } else if (action == "android.service.notification.NotificationListenerService") {
+ actNotificationListenerService = true;
+ }
+ } else if (withinProvider) {
+ if (action == "android.content.action.DOCUMENTS_PROVIDER") {
+ actDocumentsProvider = true;
}
}
if (action == "android.intent.action.SEARCH") {
@@ -1598,6 +1677,8 @@
isLauncherActivity = true;
} else if (category == "android.intent.category.LEANBACK_LAUNCHER") {
isLeanbackLauncherActivity = true;
+ } else if (category == "android.intent.category.HOME") {
+ catLauncher = true;
}
}
}
@@ -1775,37 +1856,53 @@
}
}
- if (hasMainActivity) {
- printf("main\n");
- }
if (hasWidgetReceivers) {
- printf("app-widget\n");
+ printComponentPresence("app-widget");
}
if (hasDeviceAdminReceiver) {
- printf("device-admin\n");
+ printComponentPresence("device-admin");
}
if (hasImeService) {
- printf("ime\n");
+ printComponentPresence("ime");
}
if (hasWallpaperService) {
- printf("wallpaper\n");
+ printComponentPresence("wallpaper");
}
if (hasAccessibilityService) {
- printf("accessibility\n");
+ printComponentPresence("accessibility");
}
if (hasPrintService) {
- printf("print\n");
+ printComponentPresence("print-service");
}
if (hasPaymentService) {
- printf("payment\n");
+ printComponentPresence("payment");
+ }
+ if (isSearchable) {
+ printComponentPresence("search");
+ }
+ if (hasDocumentsProvider) {
+ printComponentPresence("document-provider");
+ }
+ if (hasLauncher) {
+ printComponentPresence("launcher");
+ }
+ if (hasNotificationListenerService) {
+ printComponentPresence("notification-listener");
+ }
+ if (hasCameraActivity) {
+ printComponentPresence("camera");
+ }
+ if (hasCameraSecureActivity) {
+ printComponentPresence("camera-secure");
+ }
+
+ if (hasMainActivity) {
+ printf("main\n");
}
if (hasOtherActivities) {
printf("other-activities\n");
}
- if (isSearchable) {
- printf("search\n");
- }
- if (hasOtherReceivers) {
+ if (hasOtherReceivers) {
printf("other-receivers\n");
}
if (hasOtherServices) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
index c44e03b..ee90595 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
@@ -51,25 +51,13 @@
private final int mSrcColor;
private final Mode mMode;
- private int mWidth;
- private int mHeight;
- private BufferedImage mImage;
// ---- Public Helper methods ----
@Override
public boolean isSupported() {
- switch (mMode) {
- case CLEAR:
- case SRC:
- case SRC_IN:
- case DST_IN:
- case SRC_ATOP:
- return true;
- }
-
- return false;
+ return getAlphaCompositeRule(mMode) != -1;
}
@Override
@@ -79,9 +67,9 @@
@Override
public void applyFilter(Graphics2D g, int width, int height) {
- createFilterImage(width, height);
+ BufferedImage image = createFilterImage(width, height);
g.setComposite(getComposite());
- g.drawImage(mImage, 0, 0, null);
+ g.drawImage(image, 0, 0, null);
}
// ---- native methods ----
@@ -98,36 +86,65 @@
private PorterDuffColorFilter_Delegate(int srcColor, int mode) {
mSrcColor = srcColor;
- // Temporarily change multiply to SRC_IN to render menus.
- // TODO: support Mode.MULTIPLY
- if (mode == Mode.MULTIPLY.nativeInt) {
- mode = Mode.SRC_IN.nativeInt;
- }
- mMode = getPorterDuffMode(mode);
+ mMode = getCompatibleMode(getPorterDuffMode(mode));
}
- private void createFilterImage(int width, int height) {
- if (mWidth == width && mHeight == height && mImage != null) {
- return;
- }
- mWidth = width;
- mHeight = height;
- mImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
- Graphics2D graphics = mImage.createGraphics();
+ private BufferedImage createFilterImage(int width, int height) {
+ BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D graphics = image.createGraphics();
try {
graphics.setColor(new java.awt.Color(mSrcColor, true /* hasAlpha */));
graphics.fillRect(0, 0, width, height);
} finally {
graphics.dispose();
}
+ return image;
}
private AlphaComposite getComposite() {
- return AlphaComposite.getInstance(getAlphaCompositeRule(mMode),
- getAlpha() / 255f);
+ return AlphaComposite.getInstance(getAlphaCompositeRule(mMode));
}
- private int getAlpha() {
- return mSrcColor >>> 24;
+ // For filtering the colors, the src image should contain the "color" only for pixel values
+ // which are not transparent in the target image. But, we are using a simple rectangular image
+ // completely filled with color. Hence some AlphaComposite rules do not apply as intended.
+ // However, in such cases, they can usually be mapped to some other mode, which produces an
+ // equivalent result.
+ private Mode getCompatibleMode(Mode mode) {
+ Mode m = mode;
+ switch (mode) {
+ // Modes that are directly supported.
+ case CLEAR:
+ case DST:
+ case SRC_IN:
+ case DST_IN:
+ case DST_OUT:
+ case SRC_ATOP:
+ break;
+ // Modes that can be mapped to one of the supported modes.
+ case SRC:
+ m = Mode.SRC_IN;
+ break;
+ case SRC_OVER:
+ m = Mode.SRC_ATOP;
+ break;
+ case DST_OVER:
+ m = Mode.DST;
+ break;
+ case SRC_OUT:
+ m = Mode.CLEAR;
+ break;
+ case DST_ATOP:
+ m = Mode.DST_IN;
+ break;
+ case XOR:
+ m = Mode.DST_OUT;
+ break;
+ // This mode is not supported, but used by Action Bar Overflow Popup Menus. We map this
+ // to the closest supported mode, to prevent showing excessive warnings to the user.
+ case MULTIPLY:
+ m = Mode.SRC_IN;
+ }
+ return m;
}
}
diff --git a/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
index 1c9c40d..ee2e895 100644
--- a/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
+++ b/wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl
@@ -26,6 +26,7 @@
interface IWifiP2pManager
{
Messenger getMessenger();
+ Messenger getP2pStateMachineMessenger();
void setMiracastMode(int mode);
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 3ed2406..6409450 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -276,6 +276,13 @@
public static final String WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION =
"android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED";
+ /**
+ * The lookup key for a handover message returned by the WifiP2pService.
+ * @hide
+ */
+ public static final String EXTRA_HANDOVER_MESSAGE =
+ "android.net.wifi.p2p.EXTRA_HANDOVER_MESSAGE";
+
IWifiP2pManager mService;
private static final int BASE = Protocol.BASE_WIFI_P2P_MANAGER;
@@ -446,6 +453,21 @@
/** @hide */
public static final int SET_CHANNEL_SUCCEEDED = BASE + 73;
+ /** @hide */
+ public static final int GET_HANDOVER_REQUEST = BASE + 75;
+ /** @hide */
+ public static final int GET_HANDOVER_SELECT = BASE + 76;
+ /** @hide */
+ public static final int RESPONSE_GET_HANDOVER_MESSAGE = BASE + 77;
+ /** @hide */
+ public static final int INITIATOR_REPORT_NFC_HANDOVER = BASE + 78;
+ /** @hide */
+ public static final int RESPONDER_REPORT_NFC_HANDOVER = BASE + 79;
+ /** @hide */
+ public static final int REPORT_NFC_HANDOVER_SUCCEEDED = BASE + 80;
+ /** @hide */
+ public static final int REPORT_NFC_HANDOVER_FAILED = BASE + 81;
+
/**
* Create a new WifiP2pManager instance. Applications use
@@ -627,6 +649,14 @@
}
/**
+ * Interface for callback invocation when Handover Request or Select Message is available
+ * @hide
+ */
+ public interface HandoverMessageListener {
+ public void onHandoverMessageAvailable(String handoverMessage);
+ }
+
+ /**
* A channel that connects the application to the Wifi p2p framework.
* Most p2p operations require a Channel as an argument. An instance of Channel is obtained
* by doing a call on {@link #initialize}
@@ -687,6 +717,7 @@
case START_LISTEN_FAILED:
case STOP_LISTEN_FAILED:
case SET_CHANNEL_FAILED:
+ case REPORT_NFC_HANDOVER_FAILED:
if (listener != null) {
((ActionListener) listener).onFailure(message.arg1);
}
@@ -712,6 +743,7 @@
case START_LISTEN_SUCCEEDED:
case STOP_LISTEN_SUCCEEDED:
case SET_CHANNEL_SUCCEEDED:
+ case REPORT_NFC_HANDOVER_SUCCEEDED:
if (listener != null) {
((ActionListener) listener).onSuccess();
}
@@ -745,7 +777,17 @@
onPersistentGroupInfoAvailable(groups);
}
break;
- default:
+ case RESPONSE_GET_HANDOVER_MESSAGE:
+ Bundle handoverBundle = (Bundle) message.obj;
+ if (listener != null) {
+ String handoverMessage = handoverBundle != null
+ ? handoverBundle.getString(EXTRA_HANDOVER_MESSAGE)
+ : null;
+ ((HandoverMessageListener) listener)
+ .onHandoverMessageAvailable(handoverMessage);
+ }
+ break;
+ default:
Log.d(TAG, "Ignored " + message);
break;
}
@@ -841,7 +883,20 @@
* @return Channel instance that is necessary for performing any further p2p operations
*/
public Channel initialize(Context srcContext, Looper srcLooper, ChannelListener listener) {
- Messenger messenger = getMessenger();
+ return initalizeChannel(srcContext, srcLooper, listener, getMessenger());
+ }
+
+ /**
+ * Registers the application with the Wi-Fi framework. Enables system-only functionality.
+ * @hide
+ */
+ public Channel initializeInternal(Context srcContext, Looper srcLooper,
+ ChannelListener listener) {
+ return initalizeChannel(srcContext, srcLooper, listener, getP2pStateMachineMessenger());
+ }
+
+ private Channel initalizeChannel(Context srcContext, Looper srcLooper, ChannelListener listener,
+ Messenger messenger) {
if (messenger == null) return null;
Channel c = new Channel(srcContext, srcLooper, listener);
@@ -1327,4 +1382,62 @@
}
}
+ /**
+ * Get a reference to P2pStateMachine handler. This is used to establish
+ * a priveleged AsyncChannel communication with WifiP2pService.
+ *
+ * @return Messenger pointing to the WifiP2pService handler
+ * @hide
+ */
+ public Messenger getP2pStateMachineMessenger() {
+ try {
+ return mService.getP2pStateMachineMessenger();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Get a handover request message for use in WFA NFC Handover transfer.
+ * @hide
+ */
+ public void getNfcHandoverRequest(Channel c, HandoverMessageListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(GET_HANDOVER_REQUEST, 0, c.putListener(listener));
+ }
+
+
+ /**
+ * Get a handover select message for use in WFA NFC Handover transfer.
+ * @hide
+ */
+ public void getNfcHandoverSelect(Channel c, HandoverMessageListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(GET_HANDOVER_SELECT, 0, c.putListener(listener));
+ }
+
+ /**
+ * @hide
+ */
+ public void initiatorReportNfcHandover(Channel c, String handoverSelect,
+ ActionListener listener) {
+ checkChannel(c);
+ Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_HANDOVER_MESSAGE, handoverSelect);
+ c.mAsyncChannel.sendMessage(INITIATOR_REPORT_NFC_HANDOVER, 0,
+ c.putListener(listener), bundle);
+ }
+
+
+ /**
+ * @hide
+ */
+ public void responderReportNfcHandover(Channel c, String handoverRequest,
+ ActionListener listener) {
+ checkChannel(c);
+ Bundle bundle = new Bundle();
+ bundle.putString(EXTRA_HANDOVER_MESSAGE, handoverRequest);
+ c.mAsyncChannel.sendMessage(RESPONDER_REPORT_NFC_HANDOVER, 0,
+ c.putListener(listener), bundle);
+ }
}