diff options
33 files changed, 1030 insertions, 240 deletions
@@ -720,8 +720,9 @@ include $(BUILD_DROIDDOC) $(full_target): $(framework_built) $(gen) # Run this for checkbuild -.PHONY: checkbuild checkbuild: doc-comment-check-docs +# Check comment when you are updating the API +update-api: doc-comment-check-docs # ==== static html in the sdk ================================== include $(CLEAR_VARS) diff --git a/api/current.txt b/api/current.txt index cbffa11..24c83c1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -314,6 +314,7 @@ package android { field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b field public static final int autoLink = 16842928; // 0x10100b0 field public static final int autoMirrored = 16843754; // 0x10103ea + field public static final int autoRemoveFromRecents = 16843859; // 0x1010453 field public static final int autoStart = 16843445; // 0x10102b5 field public static final deprecated int autoText = 16843114; // 0x101016a field public static final int autoUrlDetect = 16843404; // 0x101028c @@ -463,6 +464,7 @@ package android { field public static final int dividerHorizontal = 16843564; // 0x101032c field public static final int dividerPadding = 16843562; // 0x101032a field public static final int dividerVertical = 16843530; // 0x101030a + field public static final int documentLaunchMode = 16843858; // 0x1010452 field public static final int drawSelectorOnTop = 16843004; // 0x10100fc field public static final int drawable = 16843161; // 0x1010199 field public static final int drawableBottom = 16843118; // 0x101016e @@ -3361,10 +3363,12 @@ package android.app { method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException; method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException; method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException; + method public void startLockTask(); method public deprecated void startManagingCursor(android.database.Cursor); method public boolean startNextMatchingActivity(android.content.Intent); method public boolean startNextMatchingActivity(android.content.Intent, android.os.Bundle); method public void startSearch(java.lang.String, boolean, android.os.Bundle, boolean); + method public void stopLockTask(); method public deprecated void stopManagingCursor(android.database.Cursor); method public void takeKeyEvents(boolean); method public void triggerSearch(java.lang.String, android.os.Bundle); @@ -7152,6 +7156,7 @@ package android.content { field public static final int FILL_IN_PACKAGE = 16; // 0x10 field public static final int FILL_IN_SELECTOR = 64; // 0x40 field public static final int FILL_IN_SOURCE_BOUNDS = 32; // 0x20 + field public static final int FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS = 8192; // 0x2000 field public static final int FLAG_ACTIVITY_BROUGHT_TO_FRONT = 4194304; // 0x400000 field public static final int FLAG_ACTIVITY_CLEAR_TASK = 32768; // 0x8000 field public static final int FLAG_ACTIVITY_CLEAR_TOP = 67108864; // 0x4000000 @@ -7670,8 +7675,12 @@ package android.content.pm { field public static final int CONFIG_TOUCHSCREEN = 8; // 0x8 field public static final int CONFIG_UI_MODE = 512; // 0x200 field public static final android.os.Parcelable.Creator CREATOR; + field public static final int DOCUMENT_LAUNCH_ALWAYS = 2; // 0x2 + field public static final int DOCUMENT_LAUNCH_INTO_EXISTING = 1; // 0x1 + field public static final int DOCUMENT_LAUNCH_NONE = 0; // 0x0 field public static final int FLAG_ALLOW_TASK_REPARENTING = 64; // 0x40 field public static final int FLAG_ALWAYS_RETAIN_TASK_STATE = 8; // 0x8 + field public static final int FLAG_AUTO_REMOVE_FROM_RECENTS = 8192; // 0x2000 field public static final int FLAG_CLEAR_TASK_ON_LAUNCH = 4; // 0x4 field public static final int FLAG_EXCLUDE_FROM_RECENTS = 32; // 0x20 field public static final int FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS = 256; // 0x100 @@ -7680,6 +7689,7 @@ package android.content.pm { field public static final int FLAG_IMMERSIVE = 2048; // 0x800 field public static final int FLAG_MULTIPROCESS = 1; // 0x1 field public static final int FLAG_NO_HISTORY = 128; // 0x80 + field public static final int FLAG_PERSISTABLE = 4096; // 0x1000 field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000 field public static final int FLAG_STATE_NOT_NEEDED = 16; // 0x10 field public static final int LAUNCH_MULTIPLE = 0; // 0x0 @@ -7704,6 +7714,7 @@ package android.content.pm { field public static final int SCREEN_ORIENTATION_USER_PORTRAIT = 12; // 0xc field public static final int UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW = 1; // 0x1 field public int configChanges; + field public int documentLaunchMode; field public int flags; field public int launchMode; field public java.lang.String parentActivityName; @@ -15466,12 +15477,15 @@ package android.media.session { method public void disconnect(android.media.session.RouteInfo); method public android.media.session.SessionToken getSessionToken(); method public android.media.session.TransportPerformer getTransportPerformer(); - method public void publish(); + method public boolean isActive(); method public void release(); method public void removeCallback(android.media.session.Session.Callback); method public void sendEvent(java.lang.String, android.os.Bundle); + method public void setActive(boolean); + method public void setFlags(int); method public void setRouteOptions(java.util.List<android.media.session.RouteOptions>); - method public android.media.session.TransportPerformer setTransportPerformerEnabled(); + field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1 + field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2 } public static abstract class Session.Callback { @@ -15510,7 +15524,7 @@ package android.media.session { public final class SessionManager { method public android.media.session.Session createSession(java.lang.String); - method public java.util.List<android.media.session.SessionController> getActiveSessions(); + method public java.util.List<android.media.session.SessionController> getActiveSessions(android.content.ComponentName); } public class SessionToken implements android.os.Parcelable { @@ -15700,6 +15714,7 @@ package android.net { method public android.net.NetworkInfo getActiveNetworkInfo(); method public android.net.NetworkInfo[] getAllNetworkInfo(); method public deprecated boolean getBackgroundDataSetting(); + method public android.net.ProxyInfo getGlobalProxy(); method public android.net.NetworkInfo getNetworkInfo(int); method public int getNetworkPreference(); method public boolean isActiveNetworkMetered(); @@ -15707,6 +15722,7 @@ package android.net { method public static boolean isNetworkTypeValid(int); method public void registerNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); method public boolean requestRouteToHost(int, int); + method public void setGlobalProxy(android.net.ProxyInfo); method public void setNetworkPreference(int); method public int startUsingNetworkFeature(int, java.lang.String); method public int stopUsingNetworkFeature(int, java.lang.String); @@ -15882,9 +15898,22 @@ package android.net { method public static final deprecated int getDefaultPort(); method public static final deprecated java.lang.String getHost(android.content.Context); method public static final deprecated int getPort(android.content.Context); + field public static final java.lang.String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; field public static final java.lang.String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; } + public class ProxyInfo implements android.os.Parcelable { + method public static android.net.ProxyInfo buildDirectProxy(java.lang.String, int); + method public static android.net.ProxyInfo buildDirectProxy(java.lang.String, int, java.util.List<java.lang.String>); + method public static android.net.ProxyInfo buildPacProxy(android.net.Uri); + method public int describeContents(); + method public java.lang.String[] getExclusionList(); + method public java.lang.String getHost(); + method public android.net.Uri getPacFileUrl(); + method public int getPort(); + method public void writeToParcel(android.os.Parcel, int); + } + public class SSLCertificateSocketFactory extends javax.net.ssl.SSLSocketFactory { ctor public deprecated SSLCertificateSocketFactory(int); method public java.net.Socket createSocket(java.net.Socket, java.lang.String, int, boolean) throws java.io.IOException; diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index af3a92c..4df486a 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -29,6 +29,7 @@ import com.android.internal.policy.PolicyManager; import android.annotation.IntDef; import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; import android.content.ComponentCallbacks2; import android.content.ComponentName; import android.content.ContentResolver; @@ -5689,7 +5690,16 @@ public class Activity extends ContextThemeWrapper } } - /** @hide */ + /** + * Put this Activity in a mode where the user is locked to the + * current task. + * + * This will prevent the user from launching other apps, going to settings, + * or reaching the home screen. + * + * Lock task mode will only start if the activity has been whitelisted by the + * Device Owner through {@link DevicePolicyManager#setLockTaskComponents}. + */ public void startLockTask() { try { ActivityManagerNative.getDefault().startLockTaskMode(mToken); @@ -5697,7 +5707,15 @@ public class Activity extends ContextThemeWrapper } } - /** @hide */ + /** + * Allow the user to switch away from the current task. + * + * Called to end the mode started by {@link Activity#startLockTask}. This + * can only be called by activities that have successfully called + * startLockTask previously. + * + * This will allow the user to exit this app and move onto other activities. + */ public void stopLockTask() { try { ActivityManagerNative.getDefault().stopLockTaskMode(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 3b2ff7f..b606088 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -46,7 +46,7 @@ import android.graphics.Canvas; import android.hardware.display.DisplayManagerGlobal; import android.net.IConnectivityManager; import android.net.Proxy; -import android.net.ProxyProperties; +import android.net.ProxyInfo; import android.opengl.GLUtils; import android.os.AsyncTask; import android.os.Binder; @@ -4294,8 +4294,8 @@ public final class ActivityThread { // crash if we can't get it. IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); try { - ProxyProperties proxyProperties = service.getProxy(); - Proxy.setHttpProxySystemProperty(proxyProperties); + ProxyInfo proxyInfo = service.getProxy(); + Proxy.setHttpProxySystemProperty(proxyInfo); } catch (RemoteException e) {} } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index b24b932..209c536 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2069,7 +2069,7 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param intent An intent matching the app(s) to be installed. All apps that resolve for this * intent will be re-enabled in the current profile. - * @returns int The number of activities that matched the intent and were installed. + * @return int The number of activities that matched the intent and were installed. */ public int enableSystemApp(ComponentName admin, Intent intent) { if (mService != null) { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index ae5437b..3cfc56c 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3736,7 +3736,8 @@ public class Intent implements Parcelable, Cloneable { * * <p>When set, the activity specified by this Intent will launch into a * separate task rooted at that activity. The activity launched must be - * defined with {@link android.R.attr#launchMode} "standard" or "singleTop". + * defined with {@link android.R.attr#launchMode} <code>standard</code> + * or <code>singleTop</code>. * * <p>If FLAG_ACTIVITY_NEW_DOCUMENT is used without * {@link #FLAG_ACTIVITY_MULTIPLE_TASK} then the activity manager will @@ -3751,6 +3752,8 @@ public class Intent implements Parcelable, Cloneable { * always create a new task. Thus the same document may be made to appear * more than one time in Recents. * + * <p>This is equivalent to the attribute {@link android.R.attr#documentLaunchMode}. + * * @see #FLAG_ACTIVITY_MULTIPLE_TASK */ public static final int FLAG_ACTIVITY_NEW_DOCUMENT = @@ -3815,6 +3818,15 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_ACTIVITY_TASK_ON_HOME = 0X00004000; /** + * If set and the new activity is the root of a new task, then the task + * will remain in the list of recently launched tasks only until all of + * the activities in it are finished. + * + * <p>This is equivalent to the attribute + * {@link android.R.styleable#AndroidManifestActivity_autoRemoveFromRecents}. + */ + public static final int FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS = 0x00002000; + /** * If set, when sending a broadcast only registered receivers will be * called -- no BroadcastReceiver components will be launched. */ @@ -4019,7 +4031,7 @@ public class Intent implements Parcelable, Cloneable { /** * Create an intent for a specific component with a specified action and data. - * This is equivalent using {@link #Intent(String, android.net.Uri)} to + * This is equivalent to using {@link #Intent(String, android.net.Uri)} to * construct the Intent and then calling {@link #setClass} to set its * class. * diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index c53e545..c2fe3a2 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -67,7 +67,37 @@ public class ActivityInfo extends ComponentInfo * {@link #LAUNCH_SINGLE_INSTANCE}. */ public int launchMode; - + + /** + * Constant corresponding to <code>none</code> in + * the {@link android.R.attr#documentLaunchMode} attribute. + */ + public static final int DOCUMENT_LAUNCH_NONE = 0; + /** + * Constant corresponding to <code>intoExisting</code> in + * the {@link android.R.attr#documentLaunchMode} attribute. + */ + public static final int DOCUMENT_LAUNCH_INTO_EXISTING = 1; + /** + * Constant corresponding to <code>always</code> in + * the {@link android.R.attr#documentLaunchMode} attribute. + */ + public static final int DOCUMENT_LAUNCH_ALWAYS = 2; + /** + * The document launch mode style requested by the activity. From the + * {@link android.R.attr#documentLaunchMode} attribute, one of + * {@link #DOCUMENT_LAUNCH_NONE}, {@link #DOCUMENT_LAUNCH_INTO_EXISTING}, + * {@link #DOCUMENT_LAUNCH_ALWAYS}. + * + * <p>Modes DOCUMENT_LAUNCH_ALWAYS + * and DOCUMENT_LAUNCH_INTO_EXISTING are equivalent to {@link + * android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT + * Intent.FLAG_ACTIVITY_NEW_DOCUMENT} with and without {@link + * android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK + * Intent.FLAG_ACTIVITY_MULTIPLE_TASK} respectively. + */ + public int documentLaunchMode; + /** * Optional name of a permission required to be able to access this * Activity. From the "permission" attribute. @@ -192,10 +222,15 @@ public class ActivityInfo extends ComponentInfo * Bit in {@link #flags} indicating that this activity is to be persisted across * reboots for display in the Recents list. * {@link android.R.attr#persistable} - * @hide */ public static final int FLAG_PERSISTABLE = 0x1000; /** + * Bit in {@link #flags} indicating that tasks started with this activity are to be + * removed from the recent list of tasks when the last activity in the task is finished. + * {@link android.R.attr#autoRemoveFromRecents} + */ + public static final int FLAG_AUTO_REMOVE_FROM_RECENTS = 0x2000; + /** * @hide Bit in {@link #flags}: If set, this component will only be seen * by the primary user. Only works with broadcast receivers. Set from the * android.R.attr#primaryUserOnly attribute. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 080b37b..d80ab7b 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -2462,17 +2462,6 @@ public class PackageParser { a.info.flags |= ActivityInfo.FLAG_IMMERSIVE; } - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_persistable, false)) { - a.info.flags |= ActivityInfo.FLAG_PERSISTABLE; - } - - if (sa.getBoolean( - com.android.internal.R.styleable.AndroidManifestActivity_allowEmbedded, - false)) { - a.info.flags |= ActivityInfo.FLAG_ALLOW_EMBEDDED; - } - if (!receiver) { if (sa.getBoolean( com.android.internal.R.styleable.AndroidManifestActivity_hardwareAccelerated, @@ -2483,6 +2472,9 @@ public class PackageParser { a.info.launchMode = sa.getInt( com.android.internal.R.styleable.AndroidManifestActivity_launchMode, ActivityInfo.LAUNCH_MULTIPLE); + a.info.documentLaunchMode = sa.getInt( + com.android.internal.R.styleable.AndroidManifestActivity_documentLaunchMode, + ActivityInfo.DOCUMENT_LAUNCH_NONE); a.info.screenOrientation = sa.getInt( com.android.internal.R.styleable.AndroidManifestActivity_screenOrientation, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); @@ -2492,6 +2484,23 @@ public class PackageParser { a.info.softInputMode = sa.getInt( com.android.internal.R.styleable.AndroidManifestActivity_windowSoftInputMode, 0); + + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestActivity_persistable, false)) { + a.info.flags |= ActivityInfo.FLAG_PERSISTABLE; + } + + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestActivity_allowEmbedded, + false)) { + a.info.flags |= ActivityInfo.FLAG_ALLOW_EMBEDDED; + } + + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestActivity_autoRemoveFromRecents, + false)) { + a.info.flags |= ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS; + } } else { a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE; a.info.configChanges = 0; diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 3da00b1..25708ea 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -1307,14 +1307,13 @@ public class ConnectivityManager { * doing something unusual like general internal filtering this may be useful. On * a private network where the proxy is not accessible, you may break HTTP using this. * - * @param p The a {@link ProxyProperties} object defining the new global + * @param p The a {@link ProxyInfo} object defining the new global * HTTP proxy. A {@code null} value will clear the global HTTP proxy. * * <p>This method requires the call to hold the permission * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}. - * {@hide} */ - public void setGlobalProxy(ProxyProperties p) { + public void setGlobalProxy(ProxyInfo p) { try { mService.setGlobalProxy(p); } catch (RemoteException e) { @@ -1324,14 +1323,13 @@ public class ConnectivityManager { /** * Retrieve any network-independent global HTTP proxy. * - * @return {@link ProxyProperties} for the current global HTTP proxy or {@code null} + * @return {@link ProxyInfo} for the current global HTTP proxy or {@code null} * if no global HTTP proxy is set. * * <p>This method requires the call to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. - * {@hide} */ - public ProxyProperties getGlobalProxy() { + public ProxyInfo getGlobalProxy() { try { return mService.getGlobalProxy(); } catch (RemoteException e) { @@ -1343,14 +1341,14 @@ public class ConnectivityManager { * Get the HTTP proxy settings for the current default network. Note that * if a global proxy is set, it will override any per-network setting. * - * @return the {@link ProxyProperties} for the current HTTP proxy, or {@code null} if no + * @return the {@link ProxyInfo} for the current HTTP proxy, or {@code null} if no * HTTP proxy is active. * * <p>This method requires the call to hold the permission * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}. * {@hide} */ - public ProxyProperties getProxy() { + public ProxyInfo getProxy() { try { return mService.getProxy(); } catch (RemoteException e) { diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 381a817..d53a856 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -21,7 +21,7 @@ import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkQuotaInfo; import android.net.NetworkState; -import android.net.ProxyProperties; +import android.net.ProxyInfo; import android.os.IBinder; import android.os.Messenger; import android.os.ParcelFileDescriptor; @@ -107,11 +107,11 @@ interface IConnectivityManager void reportInetCondition(int networkType, int percentage); - ProxyProperties getGlobalProxy(); + ProxyInfo getGlobalProxy(); - void setGlobalProxy(in ProxyProperties p); + void setGlobalProxy(in ProxyInfo p); - ProxyProperties getProxy(); + ProxyInfo getProxy(); void setDataDependency(int networkType, boolean met); diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index 4dfd3d9..2dcc544 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -16,7 +16,7 @@ package android.net; -import android.net.ProxyProperties; +import android.net.ProxyInfo; import android.os.Parcelable; import android.os.Parcel; import android.text.TextUtils; @@ -65,7 +65,7 @@ public class LinkProperties implements Parcelable { private ArrayList<InetAddress> mDnses = new ArrayList<InetAddress>(); private String mDomains; private ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); - private ProxyProperties mHttpProxy; + private ProxyInfo mHttpProxy; private int mMtu; // Stores the properties of links that are "stacked" above this link. @@ -101,7 +101,7 @@ public class LinkProperties implements Parcelable { mDomains = source.getDomains(); for (RouteInfo r : source.getRoutes()) mRoutes.add(r); mHttpProxy = (source.getHttpProxy() == null) ? - null : new ProxyProperties(source.getHttpProxy()); + null : new ProxyInfo(source.getHttpProxy()); for (LinkProperties l: source.mStackedLinks.values()) { addStackedLink(l); } @@ -295,10 +295,10 @@ public class LinkProperties implements Parcelable { return routes; } - public void setHttpProxy(ProxyProperties proxy) { + public void setHttpProxy(ProxyInfo proxy) { mHttpProxy = proxy; } - public ProxyProperties getHttpProxy() { + public ProxyInfo getHttpProxy() { return mHttpProxy; } @@ -720,7 +720,7 @@ public class LinkProperties implements Parcelable { netProp.addRoute((RouteInfo)in.readParcelable(null)); } if (in.readByte() == 1) { - netProp.setHttpProxy((ProxyProperties)in.readParcelable(null)); + netProp.setHttpProxy((ProxyInfo)in.readParcelable(null)); } ArrayList<LinkProperties> stackedLinks = new ArrayList<LinkProperties>(); in.readList(stackedLinks, LinkProperties.class.getClassLoader()); diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java index bea8d1c..8f41e85 100644 --- a/core/java/android/net/Proxy.java +++ b/core/java/android/net/Proxy.java @@ -19,6 +19,7 @@ package android.net; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.Context; +import android.net.ProxyInfo; import android.text.TextUtils; import android.util.Log; @@ -63,8 +64,11 @@ public final class Proxy { */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; - /** {@hide} **/ - public static final String EXTRA_PROXY_INFO = "proxy"; + /** + * Intent extra included with {@link #PROXY_CHANGE_ACTION} intents. + * It describes the new proxy being used (as a {@link ProxyInfo} object). + */ + public static final String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; /** @hide */ public static final int PROXY_VALID = 0; @@ -114,24 +118,14 @@ public final class Proxy { */ public static final java.net.Proxy getProxy(Context ctx, String url) { String host = ""; - if (url != null) { + if ((url != null) && !isLocalHost(host)) { URI uri = URI.create(url); - host = uri.getHost(); - } + ProxySelector proxySelector = ProxySelector.getDefault(); - if (!isLocalHost(host)) { - if (sConnectivityManager == null) { - sConnectivityManager = (ConnectivityManager)ctx.getSystemService( - Context.CONNECTIVITY_SERVICE); - } - if (sConnectivityManager == null) return java.net.Proxy.NO_PROXY; - - ProxyProperties proxyProperties = sConnectivityManager.getProxy(); + List<java.net.Proxy> proxyList = proxySelector.select(uri); - if (proxyProperties != null) { - if (!proxyProperties.isExcluded(host)) { - return proxyProperties.makeProxy(); - } + if (proxyList.size() > 0) { + return proxyList.get(0); } } return java.net.Proxy.NO_PROXY; @@ -275,7 +269,7 @@ public final class Proxy { } /** @hide */ - public static final void setHttpProxySystemProperty(ProxyProperties p) { + public static final void setHttpProxySystemProperty(ProxyInfo p) { String host = null; String port = null; String exclList = null; @@ -283,8 +277,8 @@ public final class Proxy { if (p != null) { host = p.getHost(); port = Integer.toString(p.getPort()); - exclList = p.getExclusionList(); - pacFileUrl = p.getPacFileUrl(); + exclList = p.getExclusionListAsString(); + pacFileUrl = p.getPacFileUrl().toString(); } setHttpProxySystemProperty(host, port, exclList, pacFileUrl); } diff --git a/core/java/android/net/ProxyProperties.aidl b/core/java/android/net/ProxyInfo.aidl index 02ea15d..2c91960 100644 --- a/core/java/android/net/ProxyProperties.aidl +++ b/core/java/android/net/ProxyInfo.aidl @@ -17,5 +17,5 @@ package android.net; -parcelable ProxyProperties; +parcelable ProxyInfo; diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyInfo.java index 50f45e8..b40941f 100644 --- a/core/java/android/net/ProxyProperties.java +++ b/core/java/android/net/ProxyInfo.java @@ -21,14 +21,23 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import org.apache.http.client.HttpClient; + import java.net.InetSocketAddress; +import java.net.URLConnection; +import java.util.List; import java.util.Locale; /** - * A container class for the http proxy info - * @hide + * Describes a proxy configuration. + * + * Proxy configurations are already integrated within the Apache HTTP stack. + * So {@link URLConnection} and {@link HttpClient} will use them automatically. + * + * Other HTTP stacks will need to obtain the proxy info from + * {@link Proxy#PROXY_CHANGE_ACTION} broadcast as the extra {@link Proxy#EXTRA_PROXY_INFO}. */ -public class ProxyProperties implements Parcelable { +public class ProxyInfo implements Parcelable { private String mHost; private int mPort; @@ -36,32 +45,82 @@ public class ProxyProperties implements Parcelable { private String[] mParsedExclusionList; private String mPacFileUrl; + /** + *@hide + */ public static final String LOCAL_EXCL_LIST = ""; + /** + *@hide + */ public static final int LOCAL_PORT = -1; + /** + *@hide + */ public static final String LOCAL_HOST = "localhost"; - public ProxyProperties(String host, int port, String exclList) { + /** + * Constructs a {@link ProxyInfo} object that points at a Direct proxy + * on the specified host and port. + */ + public static ProxyInfo buildDirectProxy(String host, int port) { + return new ProxyInfo(host, port, null); + } + + /** + * Constructs a {@link ProxyInfo} object that points at a Direct proxy + * on the specified host and port. + * + * The proxy will not be used to access any host in exclusion list, exclList. + * + * @param exclList Hosts to exclude using the proxy on connections for. These + * hosts can use wildcards such as *.example.com. + */ + public static ProxyInfo buildDirectProxy(String host, int port, List<String> exclList) { + String[] array = exclList.toArray(new String[exclList.size()]); + return new ProxyInfo(host, port, TextUtils.join(",", array), array); + } + + /** + * Construct a {@link ProxyInfo} that will download and run the PAC script + * at the specified URL. + */ + public static ProxyInfo buildPacProxy(Uri pacUri) { + return new ProxyInfo(pacUri.toString()); + } + + /** + * Create a ProxyProperties that points at a HTTP Proxy. + * @hide + */ + public ProxyInfo(String host, int port, String exclList) { mHost = host; mPort = port; setExclusionList(exclList); } - public ProxyProperties(String pacFileUrl) { + /** + * Create a ProxyProperties that points at a PAC URL. + * @hide + */ + public ProxyInfo(String pacFileUrl) { mHost = LOCAL_HOST; mPort = LOCAL_PORT; setExclusionList(LOCAL_EXCL_LIST); mPacFileUrl = pacFileUrl; } - // Only used in PacManager after Local Proxy is bound. - public ProxyProperties(String pacFileUrl, int localProxyPort) { + /** + * Only used in PacManager after Local Proxy is bound. + * @hide + */ + public ProxyInfo(String pacFileUrl, int localProxyPort) { mHost = LOCAL_HOST; mPort = localProxyPort; setExclusionList(LOCAL_EXCL_LIST); mPacFileUrl = pacFileUrl; } - private ProxyProperties(String host, int port, String exclList, String[] parsedExclList) { + private ProxyInfo(String host, int port, String exclList, String[] parsedExclList) { mHost = host; mPort = port; mExclusionList = exclList; @@ -70,16 +129,22 @@ public class ProxyProperties implements Parcelable { } // copy constructor instead of clone - public ProxyProperties(ProxyProperties source) { + /** + * @hide + */ + public ProxyInfo(ProxyInfo source) { if (source != null) { mHost = source.getHost(); mPort = source.getPort(); - mPacFileUrl = source.getPacFileUrl(); - mExclusionList = source.getExclusionList(); + mPacFileUrl = source.mPacFileUrl; + mExclusionList = source.getExclusionListAsString(); mParsedExclusionList = source.mParsedExclusionList; } } + /** + * @hide + */ public InetSocketAddress getSocketAddress() { InetSocketAddress inetSocketAddress = null; try { @@ -88,20 +153,46 @@ public class ProxyProperties implements Parcelable { return inetSocketAddress; } - public String getPacFileUrl() { - return mPacFileUrl; + /** + * Returns the URL of the current PAC script or null if there is + * no PAC script. + */ + public Uri getPacFileUrl() { + if (TextUtils.isEmpty(mPacFileUrl)) { + return null; + } + return Uri.parse(mPacFileUrl); } + /** + * When configured to use a Direct Proxy this returns the host + * of the proxy. + */ public String getHost() { return mHost; } + /** + * When configured to use a Direct Proxy this returns the port + * of the proxy + */ public int getPort() { return mPort; } - // comma separated - public String getExclusionList() { + /** + * When configured to use a Direct Proxy this returns the list + * of hosts for which the proxy is ignored. + */ + public String[] getExclusionList() { + return mParsedExclusionList; + } + + /** + * comma separated + * @hide + */ + public String getExclusionListAsString() { return mExclusionList; } @@ -111,33 +202,13 @@ public class ProxyProperties implements Parcelable { if (mExclusionList == null) { mParsedExclusionList = new String[0]; } else { - String splitExclusionList[] = exclusionList.toLowerCase(Locale.ROOT).split(","); - mParsedExclusionList = new String[splitExclusionList.length * 2]; - for (int i = 0; i < splitExclusionList.length; i++) { - String s = splitExclusionList[i].trim(); - if (s.startsWith(".")) s = s.substring(1); - mParsedExclusionList[i*2] = s; - mParsedExclusionList[(i*2)+1] = "." + s; - } + mParsedExclusionList = exclusionList.toLowerCase(Locale.ROOT).split(","); } } - public boolean isExcluded(String url) { - if (TextUtils.isEmpty(url) || mParsedExclusionList == null || - mParsedExclusionList.length == 0) return false; - - Uri u = Uri.parse(url); - String urlDomain = u.getHost(); - if (urlDomain == null) return false; - for (int i = 0; i< mParsedExclusionList.length; i+=2) { - if (urlDomain.equals(mParsedExclusionList[i]) || - urlDomain.endsWith(mParsedExclusionList[i+1])) { - return true; - } - } - return false; - } - + /** + * @hide + */ public boolean isValid() { if (!TextUtils.isEmpty(mPacFileUrl)) return true; return Proxy.PROXY_VALID == Proxy.validate(mHost == null ? "" : mHost, @@ -145,6 +216,9 @@ public class ProxyProperties implements Parcelable { mExclusionList == null ? "" : mExclusionList); } + /** + * @hide + */ public java.net.Proxy makeProxy() { java.net.Proxy proxy = java.net.Proxy.NO_PROXY; if (mHost != null) { @@ -179,17 +253,17 @@ public class ProxyProperties implements Parcelable { @Override public boolean equals(Object o) { - if (!(o instanceof ProxyProperties)) return false; - ProxyProperties p = (ProxyProperties)o; + if (!(o instanceof ProxyInfo)) return false; + ProxyInfo p = (ProxyInfo)o; // If PAC URL is present in either then they must be equal. // Other parameters will only be for fall back. if (!TextUtils.isEmpty(mPacFileUrl)) { return mPacFileUrl.equals(p.getPacFileUrl()) && mPort == p.mPort; } - if (!TextUtils.isEmpty(p.getPacFileUrl())) { + if (!TextUtils.isEmpty(p.mPacFileUrl)) { return false; } - if (mExclusionList != null && !mExclusionList.equals(p.getExclusionList())) return false; + if (mExclusionList != null && !mExclusionList.equals(p.getExclusionListAsString())) return false; if (mHost != null && p.getHost() != null && mHost.equals(p.getHost()) == false) { return false; } @@ -245,15 +319,15 @@ public class ProxyProperties implements Parcelable { * Implement the Parcelable interface. * @hide */ - public static final Creator<ProxyProperties> CREATOR = - new Creator<ProxyProperties>() { - public ProxyProperties createFromParcel(Parcel in) { + public static final Creator<ProxyInfo> CREATOR = + new Creator<ProxyInfo>() { + public ProxyInfo createFromParcel(Parcel in) { String host = null; int port = 0; if (in.readByte() != 0) { String url = in.readString(); int localPort = in.readInt(); - return new ProxyProperties(url, localPort); + return new ProxyInfo(url, localPort); } if (in.readByte() != 0) { host = in.readString(); @@ -261,13 +335,13 @@ public class ProxyProperties implements Parcelable { } String exclList = in.readString(); String[] parsedExclList = in.readStringArray(); - ProxyProperties proxyProperties = - new ProxyProperties(host, port, exclList, parsedExclList); + ProxyInfo proxyProperties = + new ProxyInfo(host, port, exclList, parsedExclList); return proxyProperties; } - public ProxyProperties[] newArray(int size) { - return new ProxyProperties[size]; + public ProxyInfo[] newArray(int size) { + return new ProxyInfo[size]; } }; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 69840c4..d8fcfc5 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -9766,6 +9766,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -9809,6 +9810,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -9852,6 +9854,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -9887,6 +9890,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -9922,6 +9926,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -10083,6 +10088,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags &= ~PFLAG_ALPHA_SET; invalidateViewProperty(true, false); mRenderNode.setAlpha(getFinalAlpha()); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } } @@ -10513,6 +10520,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, invalidateViewProperty(false, true); invalidateParentIfNeededAndWasQuickRejected(); + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -10661,6 +10669,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { mRenderNode.setOutline(null); } + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -10815,6 +10824,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidateParentIfNeeded(); } + notifySubtreeAccessibilityStateChangedIfNeeded(); } } @@ -10861,6 +10871,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidateParentIfNeeded(); } + notifySubtreeAccessibilityStateChangedIfNeeded(); } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 43bc0b6..4309366 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -4638,6 +4638,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (invalidate) { invalidateViewProperty(false, false); } + notifySubtreeAccessibilityStateChangedIfNeeded(); } /** diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index cce4dbd..b1f256e 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -885,6 +885,47 @@ be passed a persistable Bundle in their Intent.extras. --> <attr name="persistable" format="boolean" /> + <!-- Specify whether this activity should always be launched in doc-centric mode. For + values other than <code>none</code> the activity must be defined with + {@link android.R.attr#launchMode} <code>standard</code> or <code>singleTop</code>. + This attribute can be overridden by {@link + android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT}. + + <p>If this attribute is not specified, <code>none</code> will be used. + Note that this launch behavior can be changed in some ways at runtime + through the {@link android.content.Intent} flags + {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT}. --> + <attr name="documentLaunchMode"> + <!-- The default mode, which will create a new task only when + {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK + Intent.FLAG_ACTIVITY_NEW_TASK} is set. --> + <enum name="none" value="0" /> + <!-- All tasks will be searched for a matching Intent. If one is found + That task will cleared and restarted with the root activity receiving a call + to {@link android.app.Activity#onNewIntent Activity.onNewIntent}. If no + such task is found a new task will be created. + This is the equivalent of with {@link + android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT Intent.FLAG_ACTIVITY_NEW_DOCUMENT} + without {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK + Intent.FLAG_ACTIVITY_MULTIPLE_TASK}. --> + <enum name="intoExisting" value="1" /> + <!-- A new task rooted at this activity will be created. + This is the equivalent of with {@link + android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT Intent.FLAG_ACTIVITY_NEW_DOCUMENT} + paired with {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK + Intent.FLAG_ACTIVITY_MULTIPLE_TASK}. --> + <enum name="always" value="2" /> + </attr> + + <!-- Tasks launched by activities with this attribute will remain in the recent task + list until the last activity in the task is completed. When that happens the task + will be automatically removed from the recent task list. + + This attribute is the equivalent of {@link + android.content.Intent#FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS + Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS} --> + <attr name="autoRemoveFromRecents" format="boolean" /> + <!-- The <code>manifest</code> tag is the root of an <code>AndroidManifest.xml</code> file, describing the contents of an Android package (.apk) file. One @@ -1549,6 +1590,8 @@ <attr name="primaryUserOnly" format="boolean" /> <attr name="persistable" /> <attr name="allowEmbedded" /> + <attr name="documentLaunchMode" /> + <attr name="autoRemoveFromRecents" /> </declare-styleable> <!-- The <code>activity-alias</code> tag declares a new diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index e88a6ee..8874c30 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2169,6 +2169,8 @@ <public type="attr" name="excludeClass" /> <public type="attr" name="hideOnContentScroll" /> <public type="attr" name="actionOverflowMenuStyle" /> + <public type="attr" name="documentLaunchMode" /> + <public type="attr" name="autoRemoveFromRecents" /> <public-padding type="dimen" name="l_resource_pad" end="0x01050010" /> diff --git a/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml index 39c8beb..768fd9a 100644 --- a/core/res/res/values/themes_quantum.xml +++ b/core/res/res/values/themes_quantum.xml @@ -123,7 +123,7 @@ please see themes_device_defaults.xml. <item name="listSeparatorTextViewStyle">@style/Widget.Quantum.TextView.ListSeparator</item> <item name="listChoiceIndicatorSingle">@drawable/btn_radio_quantum</item> - <item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum_anim</item> + <item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum</item> <item name="listChoiceBackgroundIndicator">@drawable/list_selector_quantum</item> @@ -467,7 +467,7 @@ please see themes_device_defaults.xml. <item name="listSeparatorTextViewStyle">@style/Widget.Quantum.Light.TextView.ListSeparator</item> <item name="listChoiceIndicatorSingle">@drawable/btn_radio_quantum</item> - <item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum_anim</item> + <item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum</item> <item name="listChoiceBackgroundIndicator">@drawable/list_selector_quantum</item> diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl index ca77f04..3ff07d9 100644 --- a/media/java/android/media/session/ISession.aidl +++ b/media/java/android/media/session/ISession.aidl @@ -31,8 +31,8 @@ import android.os.ResultReceiver; interface ISession { void sendEvent(String event, in Bundle data); ISessionController getController(); - void setTransportPerformerEnabled(); - void publish(); + void setFlags(int flags); + void setActive(boolean active); void destroy(); // These commands are for setting up and communicating with routes diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index 84b9a0f..7a8c22e 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -15,6 +15,7 @@ package android.media.session; +import android.content.ComponentName; import android.media.session.ISession; import android.media.session.ISessionCallback; import android.os.Bundle; @@ -25,4 +26,5 @@ import android.os.Bundle; */ interface ISessionManager { ISession createSession(String packageName, in ISessionCallback cb, String tag); + List<IBinder> getSessions(in ComponentName compName); }
\ No newline at end of file diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java index 4ee67d1..c07229d 100644 --- a/media/java/android/media/session/MediaSessionLegacyHelper.java +++ b/media/java/android/media/session/MediaSessionLegacyHelper.java @@ -43,7 +43,8 @@ public class MediaSessionLegacyHelper { private Handler mHandler = new Handler(Looper.getMainLooper()); // The legacy APIs use PendingIntents to register/unregister media button // receivers and these are associated with RCC. - private ArrayMap<PendingIntent, SessionHolder> mSessions = new ArrayMap<PendingIntent, SessionHolder>(); + private ArrayMap<PendingIntent, SessionHolder> mSessions + = new ArrayMap<PendingIntent, SessionHolder>(); private MediaSessionLegacyHelper(Context context) { mSessionManager = (SessionManager) context @@ -78,6 +79,8 @@ public class MediaSessionLegacyHelper { } performer.addListener(listener, mHandler); holder.mRccListener = listener; + holder.mFlags |= Session.FLAG_HANDLES_TRANSPORT_CONTROLS; + holder.mSession.setFlags(holder.mFlags); holder.update(); } @@ -86,6 +89,8 @@ public class MediaSessionLegacyHelper { if (holder != null && holder.mRccListener != null) { holder.mSession.getTransportPerformer().removeListener(holder.mRccListener); holder.mRccListener = null; + holder.mFlags &= ~Session.FLAG_HANDLES_TRANSPORT_CONTROLS; + holder.mSession.setFlags(holder.mFlags); holder.update(); } } @@ -98,6 +103,8 @@ public class MediaSessionLegacyHelper { return; } holder.mMediaButtonListener = new MediaButtonListener(pi, context); + holder.mFlags |= Session.FLAG_HANDLES_MEDIA_BUTTONS; + holder.mSession.setFlags(holder.mFlags); holder.mSession.getTransportPerformer().addListener(holder.mMediaButtonListener, mHandler); } @@ -105,6 +112,9 @@ public class MediaSessionLegacyHelper { SessionHolder holder = getHolder(pi, false); if (holder != null && holder.mMediaButtonListener != null) { holder.mSession.getTransportPerformer().removeListener(holder.mMediaButtonListener); + holder.mFlags &= ~Session.FLAG_HANDLES_MEDIA_BUTTONS; + holder.mSession.setFlags(holder.mFlags); + holder.mMediaButtonListener = null; holder.update(); } } @@ -113,8 +123,7 @@ public class MediaSessionLegacyHelper { SessionHolder holder = mSessions.get(pi); if (holder == null && createIfMissing) { Session session = mSessionManager.createSession(TAG); - session.setTransportPerformerEnabled(); - session.publish(); + session.setActive(true); holder = new SessionHolder(session, pi); mSessions.put(pi, holder); } @@ -193,6 +202,7 @@ public class MediaSessionLegacyHelper { public final PendingIntent mPi; public MediaButtonListener mMediaButtonListener; public TransportPerformer.Listener mRccListener; + public int mFlags; public SessionHolder(Session session, PendingIntent pi) { mSession = session; diff --git a/media/java/android/media/session/Session.java b/media/java/android/media/session/Session.java index 8ccd788..194679e7 100644 --- a/media/java/android/media/session/Session.java +++ b/media/java/android/media/session/Session.java @@ -45,12 +45,13 @@ import java.util.List; * media to multiple routes or to provide finer grain controls of media. * <p> * A MediaSession is created by calling - * {@link SessionManager#createSession(String)}. Once a session is created - * apps that have the MEDIA_CONTENT_CONTROL permission can interact with the - * session through {@link SessionManager#getActiveSessions()}. The owner of - * the session may also use {@link #getSessionToken()} to allow apps without - * this permission to create a {@link SessionController} to interact with this - * session. + * {@link SessionManager#createSession(String)}. Once a session is created apps + * that have the MEDIA_CONTENT_CONTROL permission can interact with the session + * through + * {@link SessionManager#getActiveSessions(android.content.ComponentName)}. The + * owner of the session may also use {@link #getSessionToken()} to allow apps + * without this permission to create a {@link SessionController} to interact + * with this session. * <p> * To receive commands, media keys, and other events a Callback must be set with * {@link #addCallback(Callback)}. @@ -63,6 +64,28 @@ import java.util.List; public final class Session { private static final String TAG = "Session"; + /** + * Set this flag on the session to indicate that it can handle media button + * events. + */ + public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0; + + /** + * Set this flag on the session to indicate that it handles commands through + * the {@link TransportPerformer}. The performer can be retrieved by calling + * {@link #getTransportPerformer()}. + */ + public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1; + + /** + * System only flag for a session that needs to have priority over all other + * sessions. This flag ensures this session will receive media button events + * regardless of the current ordering in the system. + * + * @hide + */ + public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16; + private static final int MSG_MEDIA_BUTTON = 1; private static final int MSG_COMMAND = 2; private static final int MSG_ROUTE_CHANGE = 3; @@ -86,7 +109,7 @@ public final class Session { private TransportPerformer mPerformer; private Route mRoute; - private boolean mPublished = false;; + private boolean mActive = false;; /** * @hide @@ -101,6 +124,7 @@ public final class Session { throw new RuntimeException("Dead object in MediaSessionController constructor: ", e); } mSessionToken = new SessionToken(controllerBinder); + mPerformer = new TransportPerformer(mBinder); } /** @@ -148,56 +172,57 @@ public final class Session { } /** - * Start using a TransportPerformer with this media session. This must be - * called before calling publish and cannot be called more than once. - * Calling this will allow MediaControllers to retrieve a - * TransportController. + * Retrieves the {@link TransportPerformer} for this session. To receive + * commands through the performer you must also set the + * {@link #FLAG_HANDLES_TRANSPORT_CONTROLS} flag using + * {@link #setFlags(int)}. * - * @see TransportController - * @return The TransportPerformer created for this session + * @return The performer associated with this session. */ - public TransportPerformer setTransportPerformerEnabled() { - if (mPerformer != null) { - throw new IllegalStateException("setTransportPerformer can only be called once."); - } - if (mPublished) { - throw new IllegalStateException("setTransportPerformer cannot be called after publish"); - } - - mPerformer = new TransportPerformer(mBinder); - try { - mBinder.setTransportPerformerEnabled(); - } catch (RemoteException e) { - Log.wtf(TAG, "Failure in setTransportPerformerEnabled.", e); - } + public TransportPerformer getTransportPerformer() { return mPerformer; } /** - * Retrieves the TransportPerformer used by this session. If called before - * {@link #setTransportPerformerEnabled} null will be returned. + * Set any flags for the session. * - * @return The TransportPerformer associated with this session or null + * @param flags The flags to set for this session. */ - public TransportPerformer getTransportPerformer() { - return mPerformer; + public void setFlags(int flags) { + try { + mBinder.setFlags(flags); + } catch (RemoteException e) { + Log.wtf(TAG, "Failure in setFlags.", e); + } } /** - * Call after you have finished setting up the session. This will make it - * available to listeners and begin pushing updates to MediaControllers. - * This can only be called once. + * Set if this session is currently active and ready to receive commands. If + * set to false your session's controller may not be discoverable. You must + * set the session to active before it can start receiving media button + * events or transport commands. + * + * @param active Whether this session is active or not. */ - public void publish() { - if (mPublished) { - throw new RuntimeException("publish() may only be called once."); + public void setActive(boolean active) { + if (mActive == active) { + return; } try { - mBinder.publish(); + mBinder.setActive(active); + mActive = active; } catch (RemoteException e) { - Log.wtf(TAG, "Failure in publish.", e); + Log.wtf(TAG, "Failure in setActive.", e); } - mPublished = true; + } + + /** + * Get the current active state of this session. + * + * @return True if the session is active, false otherwise. + */ + public boolean isActive() { + return mActive; } /** diff --git a/media/java/android/media/session/SessionManager.java b/media/java/android/media/session/SessionManager.java index 15bf0e3..fd022fc 100644 --- a/media/java/android/media/session/SessionManager.java +++ b/media/java/android/media/session/SessionManager.java @@ -16,11 +16,13 @@ package android.media.session; +import android.content.ComponentName; import android.content.Context; import android.media.session.ISessionManager; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; +import android.service.notification.NotificationListenerService; import android.util.Log; import java.util.ArrayList; @@ -79,12 +81,27 @@ public final class SessionManager { /** * Get a list of controllers for all ongoing sessions. This requires the * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by - * the calling app. + * the calling app. You may also retrieve this list if your app is an + * enabled notification listener using the + * {@link NotificationListenerService} APIs, in which case you must pass the + * {@link ComponentName} of your enabled listener. * - * @return a list of controllers for ongoing sessions + * @param notificationListener The enabled notification listener component. + * May be null. + * @return A list of controllers for ongoing sessions */ - public List<SessionController> getActiveSessions() { - // TODO - return new ArrayList<SessionController>(); + public List<SessionController> getActiveSessions(ComponentName notificationListener) { + ArrayList<SessionController> controllers = new ArrayList<SessionController>(); + try { + List<IBinder> binders = mService.getSessions(notificationListener); + for (int i = binders.size() - 1; i >= 0; i--) { + SessionController controller = SessionController.fromBinder(ISessionController.Stub + .asInterface(binders.get(i))); + controllers.add(controller); + } + } catch (RemoteException e) { + Log.e(TAG, "Failed to get active sessions: ", e); + } + return controllers; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 0cdca66..a853b2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -526,11 +526,14 @@ public class PanelView extends FrameLayout { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: + if (mTimeAnimator.isRunning()) { + mTimeAnimator.cancel(); // end any outstanding animations + return true; + } mInitialTouchY = y; mInitialTouchX = x; initVelocityTracker(); trackMovement(event); - mTimeAnimator.cancel(); // end any outstanding animations break; case MotionEvent.ACTION_POINTER_UP: final int upPointer = event.getPointerId(event.getActionIndex()); @@ -569,7 +572,7 @@ public class PanelView extends FrameLayout { } protected boolean isScrolledToBottom() { - return false; + return true; } protected float getContentHeight() { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 45cdb65..ea03eb7 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -74,7 +74,7 @@ import android.net.NetworkStateTracker; import android.net.NetworkUtils; import android.net.Proxy; import android.net.ProxyDataTracker; -import android.net.ProxyProperties; +import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.SamplingDataTracker; import android.net.Uri; @@ -406,12 +406,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { private ArrayList mInetLog; // track the current default http proxy - tell the world if we get a new one (real change) - private ProxyProperties mDefaultProxy = null; + private ProxyInfo mDefaultProxy = null; private Object mProxyLock = new Object(); private boolean mDefaultProxyDisabled = false; // track the global proxy. - private ProxyProperties mGlobalProxy = null; + private ProxyInfo mGlobalProxy = null; private PacManager mPacManager = null; @@ -3192,7 +3192,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { break; } case EVENT_PROXY_HAS_CHANGED: { - handleApplyDefaultProxy((ProxyProperties)msg.obj); + handleApplyDefaultProxy((ProxyInfo)msg.obj); break; } } @@ -3410,19 +3410,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { return; } - public ProxyProperties getProxy() { + public ProxyInfo getProxy() { // this information is already available as a world read/writable jvm property // so this API change wouldn't have a benifit. It also breaks the passing // of proxy info to all the JVMs. // enforceAccessPermission(); synchronized (mProxyLock) { - ProxyProperties ret = mGlobalProxy; + ProxyInfo ret = mGlobalProxy; if ((ret == null) && !mDefaultProxyDisabled) ret = mDefaultProxy; return ret; } } - public void setGlobalProxy(ProxyProperties proxyProperties) { + public void setGlobalProxy(ProxyInfo proxyProperties) { enforceConnectivityInternalPermission(); synchronized (mProxyLock) { @@ -3435,18 +3435,18 @@ public class ConnectivityService extends IConnectivityManager.Stub { String exclList = ""; String pacFileUrl = ""; if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) || - !TextUtils.isEmpty(proxyProperties.getPacFileUrl()))) { + (proxyProperties.getPacFileUrl() != null))) { if (!proxyProperties.isValid()) { if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString()); return; } - mGlobalProxy = new ProxyProperties(proxyProperties); + mGlobalProxy = new ProxyInfo(proxyProperties); host = mGlobalProxy.getHost(); port = mGlobalProxy.getPort(); - exclList = mGlobalProxy.getExclusionList(); + exclList = mGlobalProxy.getExclusionListAsString(); if (proxyProperties.getPacFileUrl() != null) { - pacFileUrl = proxyProperties.getPacFileUrl(); + pacFileUrl = proxyProperties.getPacFileUrl().toString(); } } else { mGlobalProxy = null; @@ -3478,11 +3478,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST); String pacFileUrl = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC); if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(pacFileUrl)) { - ProxyProperties proxyProperties; + ProxyInfo proxyProperties; if (!TextUtils.isEmpty(pacFileUrl)) { - proxyProperties = new ProxyProperties(pacFileUrl); + proxyProperties = new ProxyInfo(pacFileUrl); } else { - proxyProperties = new ProxyProperties(host, port, exclList); + proxyProperties = new ProxyInfo(host, port, exclList); } if (!proxyProperties.isValid()) { if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString()); @@ -3495,7 +3495,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - public ProxyProperties getGlobalProxy() { + public ProxyInfo getGlobalProxy() { // this information is already available as a world read/writable jvm property // so this API change wouldn't have a benifit. It also breaks the passing // of proxy info to all the JVMs. @@ -3505,9 +3505,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - private void handleApplyDefaultProxy(ProxyProperties proxy) { + private void handleApplyDefaultProxy(ProxyInfo proxy) { if (proxy != null && TextUtils.isEmpty(proxy.getHost()) - && TextUtils.isEmpty(proxy.getPacFileUrl())) { + && (proxy.getPacFileUrl() == null)) { proxy = null; } synchronized (mProxyLock) { @@ -3544,13 +3544,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { return; } } - ProxyProperties p = new ProxyProperties(data[0], proxyPort, ""); + ProxyInfo p = new ProxyInfo(data[0], proxyPort, ""); setGlobalProxy(p); } } - private void sendProxyBroadcast(ProxyProperties proxy) { - if (proxy == null) proxy = new ProxyProperties("", 0, ""); + private void sendProxyBroadcast(ProxyInfo proxy) { + if (proxy == null) proxy = new ProxyInfo("", 0, ""); if (mPacManager.setCurrentProxyScriptUrl(proxy)) return; if (DBG) log("sending Proxy Broadcast for " + proxy); Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index cbb8377..0a02e17 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -136,7 +136,7 @@ import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.net.Proxy; -import android.net.ProxyProperties; +import android.net.ProxyInfo; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -1323,7 +1323,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } break; case UPDATE_HTTP_PROXY_MSG: { - ProxyProperties proxy = (ProxyProperties)msg.obj; + ProxyInfo proxy = (ProxyInfo)msg.obj; String host = ""; String port = ""; String exclList = ""; @@ -1331,8 +1331,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (proxy != null) { host = proxy.getHost(); port = Integer.toString(proxy.getPort()); - exclList = proxy.getExclusionList(); - pacFileUrl = proxy.getPacFileUrl(); + exclList = proxy.getExclusionListAsString(); + pacFileUrl = proxy.getPacFileUrl().toString(); } synchronized (ActivityManagerService.this) { for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { @@ -13758,7 +13758,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) { - ProxyProperties proxy = intent.getParcelableExtra("proxy"); + ProxyInfo proxy = intent.getParcelableExtra("proxy"); mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy)); } diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java index 8815d0f..0749f24 100644 --- a/services/core/java/com/android/server/connectivity/PacManager.java +++ b/services/core/java/com/android/server/connectivity/PacManager.java @@ -24,7 +24,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; -import android.net.ProxyProperties; +import android.net.ProxyInfo; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; @@ -157,14 +157,14 @@ public class PacManager { * @param proxy Proxy information that is about to be broadcast. * @return Returns true when the broadcast should not be sent */ - public synchronized boolean setCurrentProxyScriptUrl(ProxyProperties proxy) { - if (!TextUtils.isEmpty(proxy.getPacFileUrl())) { + public synchronized boolean setCurrentProxyScriptUrl(ProxyInfo proxy) { + if (proxy.getPacFileUrl() != null) { if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) { // Allow to send broadcast, nothing to do. return false; } synchronized (mProxyLock) { - mPacUrl = proxy.getPacFileUrl(); + mPacUrl = proxy.getPacFileUrl().toString(); } mCurrentDelay = DELAY_1; mHasSentBroadcast = false; @@ -268,7 +268,7 @@ public class PacManager { // Already bound no need to bind again. if ((mProxyConnection != null) && (mConnection != null)) { if (mLastPort != -1) { - sendPacBroadcast(new ProxyProperties(mPacUrl, mLastPort)); + sendPacBroadcast(new ProxyInfo(mPacUrl, mLastPort)); } else { Log.e(TAG, "Received invalid port from Local Proxy," + " PAC will not be operational"); @@ -362,7 +362,7 @@ public class PacManager { mLastPort = -1; } - private void sendPacBroadcast(ProxyProperties proxy) { + private void sendPacBroadcast(ProxyInfo proxy) { mConnectivityHandler.sendMessage(mConnectivityHandler.obtainMessage(mProxyMessage, proxy)); } @@ -371,7 +371,7 @@ public class PacManager { return; } if (!mHasSentBroadcast) { - sendPacBroadcast(new ProxyProperties(mPacUrl, mLastPort)); + sendPacBroadcast(new ProxyInfo(mPacUrl, mLastPort)); mHasSentBroadcast = true; } } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 3dc17fc..015032b 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -17,6 +17,7 @@ package com.android.server.media; import android.content.Intent; +import android.content.pm.PackageManager; import android.media.routeprovider.RouteRequest; import android.media.session.ISessionController; import android.media.session.ISessionControllerCallback; @@ -59,6 +60,24 @@ import java.util.UUID; public class MediaSessionRecord implements IBinder.DeathRecipient { private static final String TAG = "MediaSessionRecord"; + /** + * These are the playback states that count as currently active. + */ + private static final int[] ACTIVE_STATES = { + PlaybackState.PLAYSTATE_FAST_FORWARDING, + PlaybackState.PLAYSTATE_REWINDING, + PlaybackState.PLAYSTATE_SKIPPING_BACKWARDS, + PlaybackState.PLAYSTATE_SKIPPING_FORWARDS, + PlaybackState.PLAYSTATE_BUFFERING, + PlaybackState.PLAYSTATE_CONNECTING, + PlaybackState.PLAYSTATE_PLAYING }; + + /** + * The length of time a session will still be considered active after + * pausing in ms. + */ + private static final int ACTIVE_BUFFER = 30000; + private final MessageHandler mHandler; private final int mPid; @@ -74,21 +93,22 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { new ArrayList<ISessionControllerCallback>(); private final ArrayList<RouteRequest> mRequests = new ArrayList<RouteRequest>(); - private boolean mTransportPerformerEnabled = false; private RouteInfo mRoute; private RouteOptions mRequest; private RouteConnectionRecord mConnection; // TODO define a RouteState class with relevant info private int mRouteState; + private long mFlags; // TransportPerformer fields private MediaMetadata mMetadata; private PlaybackState mPlaybackState; private int mRatingType; + private long mLastActiveTime; // End TransportPerformer fields - private boolean mIsPublished = false; + private boolean mIsActive = false; public MediaSessionRecord(int pid, String packageName, ISessionCallback cb, String tag, MediaSessionService service, Handler handler) { @@ -148,6 +168,35 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } /** + * Get this session's flags. + * + * @return The flags for this session. + */ + public long getFlags() { + return mFlags; + } + + /** + * Check if this session has the specified flag. + * + * @param flag The flag to check. + * @return True if this session has that flag set, false otherwise. + */ + public boolean hasFlag(int flag) { + return (mFlags & flag) != 0; + } + + /** + * Check if this session has system priorty and should receive media buttons + * before any other sessions. + * + * @return True if this is a system priority session, false otherwise + */ + public boolean isSystemPriority() { + return (mFlags & Session.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0; + } + + /** * Set the selected route. This does not connect to the route, just notifies * the app that a new route has been selected. * @@ -215,12 +264,36 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } /** - * Check if this session has been published by the app yet. + * Check if this session has been set to active by the app. + * + * @return True if the session is active, false otherwise. + */ + public boolean isActive() { + return mIsActive; + } + + /** + * Check if the session is currently performing playback. This will also + * return true if the session was recently paused. * - * @return True if it has been published, false otherwise. + * @return True if the session is performing playback, false otherwise. */ - public boolean isPublished() { - return mIsPublished; + public boolean isPlaybackActive() { + int state = mPlaybackState == null ? 0 : mPlaybackState.getState(); + if (isActiveState(state)) { + return true; + } + if (state == mPlaybackState.PLAYSTATE_PAUSED) { + long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime; + if (inactiveTime < ACTIVE_BUFFER) { + return true; + } + } + return false; + } + + public boolean isTransportControlEnabled() { + return hasFlag(Session.FLAG_HANDLES_TRANSPORT_CONTROLS); } @Override @@ -234,11 +307,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { final String indent = prefix + " "; pw.println(indent + "pid=" + mPid); pw.println(indent + "info=" + mSessionInfo.toString()); - pw.println(indent + "published=" + mIsPublished); - pw.println(indent + "transport controls enabled=" + mTransportPerformerEnabled); + pw.println(indent + "published=" + mIsActive); + pw.println(indent + "flags=" + mFlags); pw.println(indent + "rating type=" + mRatingType); pw.println(indent + "controllers: " + mControllerCallbacks.size()); - pw.println(indent + "state=" + mPlaybackState.toString()); + pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString())); pw.println(indent + "metadata:" + getShortMetadataString()); pw.println(indent + "route requests {"); int size = mRequests.size(); @@ -251,6 +324,15 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { pw.println(indent + "params=" + (mRequest == null ? null : mRequest.toString())); } + private boolean isActiveState(int state) { + for (int i = 0; i < ACTIVE_STATES.length; i++) { + if (ACTIVE_STATES[i] == state) { + return true; + } + } + return false; + } + private String getShortMetadataString() { int fields = mMetadata == null ? 0 : mMetadata.size(); String title = mMetadata == null ? null : mMetadata @@ -393,12 +475,21 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override - public void publish() { - mIsPublished = true; // TODO push update to service + public void setActive(boolean active) { + mIsActive = active; + mService.updateSession(MediaSessionRecord.this); + mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); } + @Override - public void setTransportPerformerEnabled() { - mTransportPerformerEnabled = true; + public void setFlags(int flags) { + if ((flags & Session.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) { + int pid = getCallingPid(); + int uid = getCallingUid(); + mService.enforcePhoneStatePermission(pid, uid); + } + mFlags = flags; + mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); } @Override @@ -409,7 +500,13 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { @Override public void setPlaybackState(PlaybackState state) { + int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState(); + int newState = state == null ? 0 : state.getState(); + if (isActiveState(oldState) && newState == PlaybackState.PLAYSTATE_PAUSED) { + mLastActiveTime = SystemClock.elapsedRealtime(); + } mPlaybackState = state; + mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState); mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE); } @@ -673,7 +770,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { @Override public boolean isTransportControlEnabled() { - return mTransportPerformerEnabled; + return MediaSessionRecord.this.isTransportControlEnabled(); } @Override @@ -689,6 +786,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private static final int MSG_SEND_EVENT = 4; 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; public MessageHandler(Looper looper) { super(looper); @@ -713,6 +811,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { (Pair<RouteCommand, ResultReceiver>) msg.obj; pushRouteCommand(cmd.first, cmd.second); break; + case MSG_UPDATE_SESSION_STATE: + // TODO add session state + break; } } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 107f6ad..fb858fc 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -17,17 +17,25 @@ package com.android.server.media; import android.Manifest; +import android.app.ActivityManager; +import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.media.routeprovider.RouteRequest; import android.media.session.ISession; import android.media.session.ISessionCallback; +import android.media.session.ISessionController; import android.media.session.ISessionManager; +import android.media.session.PlaybackState; import android.media.session.RouteInfo; import android.media.session.RouteOptions; import android.os.Binder; import android.os.Handler; +import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; import android.text.TextUtils; import android.util.Log; @@ -38,6 +46,7 @@ import com.android.server.Watchdog.Monitor; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; /** * System implementation of MediaSessionManager @@ -48,15 +57,17 @@ public class MediaSessionService extends SystemService implements Monitor { private final SessionManagerImpl mSessionManagerImpl; private final MediaRouteProviderWatcher mRouteProviderWatcher; + private final MediaSessionStack mPriorityStack; - private final ArrayList<MediaSessionRecord> mSessions - = new ArrayList<MediaSessionRecord>(); + private final ArrayList<MediaSessionRecord> mRecords = new ArrayList<MediaSessionRecord>(); private final ArrayList<MediaRouteProviderProxy> mProviders = new ArrayList<MediaRouteProviderProxy>(); private final Object mLock = new Object(); // TODO do we want a separate thread for handling mediasession messages? private final Handler mHandler = new Handler(); + private MediaSessionRecord mPrioritySession; + // Used to keep track of the current request to show routes for a specific // session so we drop late callbacks properly. private int mShowRoutesRequestId = 0; @@ -69,6 +80,7 @@ public class MediaSessionService extends SystemService implements Monitor { mSessionManagerImpl = new SessionManagerImpl(); mRouteProviderWatcher = new MediaRouteProviderWatcher(context, mProviderWatcherCallback, mHandler, context.getUserId()); + mPriorityStack = new MediaSessionStack(); } @Override @@ -121,6 +133,30 @@ public class MediaSessionService extends SystemService implements Monitor { } } + public void updateSession(MediaSessionRecord record) { + synchronized (mLock) { + mPriorityStack.onSessionStateChange(record); + if (record.isSystemPriority()) { + if (record.isActive()) { + if (mPrioritySession != null) { + Log.w(TAG, "Replacing existing priority session with a new session"); + } + mPrioritySession = record; + } else { + if (mPrioritySession == record) { + mPrioritySession = null; + } + } + } + } + } + + public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) { + synchronized (mLock) { + mPriorityStack.onPlaystateChange(record, oldState, newState); + } + } + @Override public void monitor() { synchronized (mLock) { @@ -141,7 +177,11 @@ public class MediaSessionService extends SystemService implements Monitor { } private void destroySessionLocked(MediaSessionRecord session) { - mSessions.remove(session); + mRecords.remove(session); + mPriorityStack.removeSession(session); + if (session == mPrioritySession) { + mPrioritySession = null; + } } private void enforcePackageName(String packageName, int uid) { @@ -158,8 +198,64 @@ public class MediaSessionService extends SystemService implements Monitor { throw new IllegalArgumentException("packageName is not owned by the calling process"); } + protected void enforcePhoneStatePermission(int pid, int uid) { + if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission."); + } + } + + /** + * Checks a caller's authorization to register an IRemoteControlDisplay. + * Authorization is granted if one of the following is true: + * <ul> + * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL + * permission</li> + * <li>the caller's listener is one of the enabled notification listeners</li> + * </ul> + */ + private void enforceMediaPermissions(ComponentName compName, int pid, int uid) { + if (getContext() + .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid) + != PackageManager.PERMISSION_GRANTED + && !isEnabledNotificationListener(compName)) { + throw new SecurityException("Missing permission to control media."); + } + } + + private boolean isEnabledNotificationListener(ComponentName compName) { + if (compName != null) { + final int currentUser = ActivityManager.getCurrentUser(); + final String enabledNotifListeners = Settings.Secure.getStringForUser( + getContext().getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + currentUser); + if (enabledNotifListeners != null) { + final String[] components = enabledNotifListeners.split(":"); + for (int i = 0; i < components.length; i++) { + final ComponentName component = + ComponentName.unflattenFromString(components[i]); + if (component != null) { + if (compName.equals(component)) { + if (DEBUG) { + Log.d(TAG, "ok to get sessions: " + component + + " is authorized notification listener"); + } + return true; + } + } + } + } + if (DEBUG) { + Log.d(TAG, "not ok to get sessions, " + compName + + " is not in list of ENABLED_NOTIFICATION_LISTENERS"); + } + } + return false; + } + private MediaSessionRecord createSessionInternal(int pid, String packageName, - ISessionCallback cb, String tag) { + ISessionCallback cb, String tag, boolean forCalls) { synchronized (mLock) { return createSessionLocked(pid, packageName, cb, tag); } @@ -174,13 +270,24 @@ public class MediaSessionService extends SystemService implements Monitor { } catch (RemoteException e) { throw new RuntimeException("Media Session owner died prematurely.", e); } - mSessions.add(session); + mRecords.add(session); + mPriorityStack.addSession(session); if (DEBUG) { Log.d(TAG, "Created session for package " + packageName + " with tag " + tag); } return session; } + private int findIndexOfSessionForIdLocked(String sessionId) { + for (int i = mRecords.size() - 1; i >= 0; i--) { + MediaSessionRecord session = mRecords.get(i); + if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) { + return i; + } + } + return -1; + } + private MediaRouteProviderProxy getProviderLocked(String providerId) { for (int i = mProviders.size() - 1; i >= 0; i--) { MediaRouteProviderProxy provider = mProviders.get(i); @@ -191,14 +298,9 @@ public class MediaSessionService extends SystemService implements Monitor { return null; } - private int findIndexOfSessionForIdLocked(String sessionId) { - for (int i = mSessions.size() - 1; i >= 0; i--) { - MediaSessionRecord session = mSessions.get(i); - if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) { - return i; - } - } - return -1; + private boolean isSessionDiscoverable(MediaSessionRecord record) { + // TODO probably want to check more than if it's published. + return record.isActive(); } private MediaRouteProviderWatcher.Callback mProviderWatcherCallback @@ -232,7 +334,7 @@ public class MediaSessionService extends SystemService implements Monitor { synchronized (mLock) { int index = findIndexOfSessionForIdLocked(sessionId); if (index != -1 && routes != null && routes.size() > 0) { - MediaSessionRecord record = mSessions.get(index); + MediaSessionRecord record = mRecords.get(index); record.selectRoute(routes.get(0)); } } @@ -244,7 +346,7 @@ public class MediaSessionService extends SystemService implements Monitor { synchronized (mLock) { int index = findIndexOfSessionForIdLocked(sessionId); if (index != -1) { - MediaSessionRecord session = mSessions.get(index); + MediaSessionRecord session = mRecords.get(index); session.setRouteConnected(route, options.getConnectionOptions(), connection); } } @@ -266,7 +368,37 @@ public class MediaSessionService extends SystemService implements Monitor { if (cb == null) { throw new IllegalArgumentException("Controller callback cannot be null"); } - return createSessionInternal(pid, packageName, cb, tag).getSessionBinder(); + return createSessionInternal(pid, packageName, cb, tag, false).getSessionBinder(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public List<IBinder> getSessions(ComponentName componentName) { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + + try { + if (componentName != null) { + // If they gave us a component name verify they own the + // package + enforcePackageName(componentName.getPackageName(), uid); + } + // Then check if they have the permissions or their component is + // allowed + enforceMediaPermissions(componentName, pid, uid); + ArrayList<IBinder> binders = new ArrayList<IBinder>(); + synchronized (mLock) { + ArrayList<MediaSessionRecord> records = mPriorityStack + .getActiveSessions(); + int size = records.size(); + for (int i = 0; i < size; i++) { + binders.add(records.get(i).getControllerBinder().asBinder()); + } + } + return binders; } finally { Binder.restoreCallingIdentity(token); } @@ -286,13 +418,18 @@ public class MediaSessionService extends SystemService implements Monitor { pw.println(); synchronized (mLock) { - int count = mSessions.size(); - pw.println("Sessions - have " + count + " states:"); + pw.println("Session for calls:" + mPrioritySession); + if (mPrioritySession != null) { + mPrioritySession.dump(pw, ""); + } + int count = mRecords.size(); + pw.println(count + " Sessions:"); for (int i = 0; i < count; i++) { - MediaSessionRecord record = mSessions.get(i); + mRecords.get(i).dump(pw, ""); pw.println(); - record.dump(pw, ""); } + mPriorityStack.dumpLocked(pw, ""); + pw.println("Providers:"); count = mProviders.size(); for (int i = 0; i < count; i++) { diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java new file mode 100644 index 0000000..f9f004d --- /dev/null +++ b/services/core/java/com/android/server/media/MediaSessionStack.java @@ -0,0 +1,267 @@ +/* + * 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.media; + +import android.media.session.PlaybackState; +import android.media.session.Session; +import android.text.TextUtils; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Keeps track of media sessions and their priority for notifications, media + * button routing, etc. + */ +public class MediaSessionStack { + /** + * These are states that usually indicate the user took an action and should + * bump priority regardless of the old state. + */ + private static final int[] ALWAYS_PRIORITY_STATES = { + PlaybackState.PLAYSTATE_FAST_FORWARDING, + PlaybackState.PLAYSTATE_REWINDING, + PlaybackState.PLAYSTATE_SKIPPING_BACKWARDS, + PlaybackState.PLAYSTATE_SKIPPING_FORWARDS }; + /** + * These are states that usually indicate the user took an action if they + * were entered from a non-priority state. + */ + private static final int[] TRANSITION_PRIORITY_STATES = { + PlaybackState.PLAYSTATE_BUFFERING, + PlaybackState.PLAYSTATE_CONNECTING, + PlaybackState.PLAYSTATE_PLAYING }; + + private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>(); + + private MediaSessionRecord mCachedButtonReceiver; + private MediaSessionRecord mCachedDefault; + private ArrayList<MediaSessionRecord> mCachedActiveList; + private ArrayList<MediaSessionRecord> mCachedTransportControlList; + + /** + * Add a record to the priority tracker. + * + * @param record The record to add. + */ + public void addSession(MediaSessionRecord record) { + mSessions.add(record); + clearCache(); + } + + /** + * Remove a record from the priority tracker. + * + * @param record The record to remove. + */ + public void removeSession(MediaSessionRecord record) { + mSessions.remove(record); + clearCache(); + } + + /** + * Notify the priority tracker that a session's state changed. + * + * @param record The record that changed. + * @param oldState Its old playback state. + * @param newState Its new playback state. + */ + public void onPlaystateChange(MediaSessionRecord record, int oldState, int newState) { + if (shouldUpdatePriority(oldState, newState)) { + mSessions.remove(record); + mSessions.add(0, record); + clearCache(); + } + } + + /** + * Handle any stack changes that need to occur in response to a session + * state change. TODO add the old and new session state as params + * + * @param record The record that changed. + */ + public void onSessionStateChange(MediaSessionRecord record) { + // For now just clear the cache. Eventually we'll selectively clear + // depending on what changed. + clearCache(); + } + + /** + * Get the current priority sorted list of active sessions. The most + * important session is at index 0 and the least important at size - 1. + * + * @return All the active sessions in priority order. + */ + public ArrayList<MediaSessionRecord> getActiveSessions() { + if (mCachedActiveList == null) { + mCachedActiveList = getPriorityListLocked(true, 0); + } + return mCachedActiveList; + } + + /** + * Get the current priority sorted list of active sessions that use + * transport controls. The most important session is at index 0 and the + * least important at size -1. + * + * @return All the active sessions that handle transport controls in + * priority order. + */ + public ArrayList<MediaSessionRecord> getTransportControlSessions() { + if (mCachedTransportControlList == null) { + mCachedTransportControlList = getPriorityListLocked(true, + Session.FLAG_HANDLES_TRANSPORT_CONTROLS); + } + return mCachedTransportControlList; + } + + /** + * Get the highest priority active session. + * + * @return The current highest priority session or null. + */ + public MediaSessionRecord getDefaultSession() { + if (mCachedDefault != null) { + return mCachedDefault; + } + ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0); + if (records.size() > 0) { + return records.get(0); + } + return null; + } + + /** + * Get the highest priority session that can handle media buttons. + * + * @return The default media button session or null. + */ + public MediaSessionRecord getDefaultMediaButtonSession() { + if (mCachedButtonReceiver != null) { + return mCachedButtonReceiver; + } + ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, + Session.FLAG_HANDLES_MEDIA_BUTTONS); + if (records.size() > 0) { + mCachedButtonReceiver = records.get(0); + } + return mCachedButtonReceiver; + } + + public void dumpLocked(PrintWriter pw, String prefix) { + ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0); + int count = sortedSessions.size(); + pw.println(prefix + "Sessions Stack - have " + count + " sessions:"); + String indent = prefix + " "; + for (int i = 0; i < count; i++) { + MediaSessionRecord record = sortedSessions.get(i); + record.dump(pw, indent); + pw.println(); + } + } + + /** + * Get a priority sorted list of sessions. Can filter to only return active + * sessions or sessions with specific flags. + * + * @param activeOnly True to only return active sessions, false to return + * all sessions. + * @param withFlags Only return sessions with all the specified flags set. 0 + * returns all sessions. + * @return The priority sorted list of sessions. + */ + private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags) { + ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>(); + int lastLocalIndex = 0; + int lastActiveIndex = 0; + int lastPublishedIndex = 0; + + int size = mSessions.size(); + for (int i = 0; i < size; i++) { + final MediaSessionRecord session = mSessions.get(i); + + if ((session.getFlags() & withFlags) != withFlags) { + continue; + } + if (!session.isActive()) { + if (!activeOnly) { + // If we're getting unpublished as well always put them at + // the end + result.add(session); + } + continue; + } + + if (session.isSystemPriority()) { + // System priority sessions are special and always go at the + // front. We expect there to only be one of these at a time. + result.add(0, session); + lastLocalIndex++; + lastActiveIndex++; + lastPublishedIndex++; + } else if (session.isPlaybackActive()) { + // TODO replace getRoute() == null with real local route check + if(session.getRoute() == null) { + // Active local sessions get top priority + result.add(lastLocalIndex, session); + lastLocalIndex++; + lastActiveIndex++; + lastPublishedIndex++; + } else { + // Then active remote sessions + result.add(lastActiveIndex, session); + lastActiveIndex++; + lastPublishedIndex++; + } + } else { + // inactive sessions go at the end in order of whoever last did + // something. + result.add(lastPublishedIndex, session); + lastPublishedIndex++; + } + } + + return result; + } + + private boolean shouldUpdatePriority(int oldState, int newState) { + if (containsState(newState, ALWAYS_PRIORITY_STATES)) { + return true; + } + if (!containsState(oldState, TRANSITION_PRIORITY_STATES) + && containsState(newState, TRANSITION_PRIORITY_STATES)) { + return true; + } + return false; + } + + private boolean containsState(int state, int[] states) { + for (int i = 0; i < states.length; i++) { + if (states[i] == state) { + return true; + } + } + return false; + } + + private void clearCache() { + mCachedDefault = null; + mCachedButtonReceiver = null; + mCachedActiveList = null; + mCachedTransportControlList = null; + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 23b912f..dcca837 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -50,7 +50,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; -import android.net.ProxyProperties; +import android.net.ProxyInfo; import android.os.Binder; import android.os.Bundle; import android.os.Environment; @@ -2533,7 +2533,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { exclusionList = exclusionList.trim(); ContentResolver res = mContext.getContentResolver(); - ProxyProperties proxyProperties = new ProxyProperties(data[0], proxyPort, exclusionList); + ProxyInfo proxyProperties = new ProxyInfo(data[0], proxyPort, exclusionList); if (!proxyProperties.isValid()) { Slog.e(LOG_TAG, "Invalid proxy properties, ignoring: " + proxyProperties.toString()); return; diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java index 2e029f0..b7dcef7 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java @@ -84,11 +84,12 @@ public class PlayerSession { Log.d(TAG, "Creating session for package " + mContext.getBasePackageName()); mSession = man.createSession("OneMedia"); mSession.addCallback(mCallback); - mPerformer = mSession.setTransportPerformerEnabled(); + mPerformer = mSession.getTransportPerformer(); mPerformer.addListener(new TransportListener()); mPerformer.setPlaybackState(mPlaybackState); + mSession.setFlags(Session.FLAG_HANDLES_TRANSPORT_CONTROLS); mSession.setRouteOptions(mRouteOptions); - mSession.publish(); + mSession.setActive(true); } public void onDestroy() { |
