Merge "UiBench adapted for TV" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index f19ac5c..44df333 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22997,6 +22997,7 @@
field public static final java.lang.String ACTION_BLOCKED_RATINGS_CHANGED = "android.media.tv.action.BLOCKED_RATINGS_CHANGED";
field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
+ field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
@@ -26888,6 +26889,7 @@
method public static void glProgramBinary(int, int, java.nio.Buffer, int);
method public static void glProgramParameteri(int, int, int);
method public static void glReadBuffer(int);
+ method public static void glReadPixels(int, int, int, int, int, int, int);
method public static void glRenderbufferStorageMultisample(int, int, int, int, int);
method public static void glResumeTransformFeedback();
method public static void glSamplerParameterf(int, int, float);
@@ -36140,6 +36142,7 @@
method public final void conferenceRemoteConnections(android.telecom.RemoteConnection, android.telecom.RemoteConnection);
method public final android.telecom.RemoteConnection createRemoteIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
method public final android.telecom.RemoteConnection createRemoteOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+ method public final java.util.Collection<android.telecom.Conference> getAllConferences();
method public final java.util.Collection<android.telecom.Connection> getAllConnections();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConference(android.telecom.Connection, android.telecom.Connection);
@@ -36508,9 +36511,11 @@
package android.telephony {
public class CarrierConfigManager {
+ method public android.os.PersistableBundle getConfig(int);
method public android.os.PersistableBundle getConfig();
- method public android.os.PersistableBundle getConfigForSubId(int);
- method public void notifyConfigChangedForSubId(int);
+ method public deprecated android.os.PersistableBundle getConfigForSubId(int);
+ method public void notifyConfigChanged(int);
+ method public deprecated void notifyConfigChangedForSubId(int);
field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
field public static final java.lang.String BOOL_ALLOW_EMERGENCY_VIDEO_CALLS = "bool_allow_emergency_video_calls";
field public static final java.lang.String BOOL_ALLOW_VIDEO_PAUSE = "bool_allow_video_pause";
diff --git a/api/system-current.txt b/api/system-current.txt
index b534b1e..79d0cd7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -24708,6 +24708,7 @@
field public static final java.lang.String ACTION_BLOCKED_RATINGS_CHANGED = "android.media.tv.action.BLOCKED_RATINGS_CHANGED";
field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
+ field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
@@ -29188,6 +29189,7 @@
method public static void glProgramBinary(int, int, java.nio.Buffer, int);
method public static void glProgramParameteri(int, int, int);
method public static void glReadBuffer(int);
+ method public static void glReadPixels(int, int, int, int, int, int, int);
method public static void glRenderbufferStorageMultisample(int, int, int, int, int);
method public static void glResumeTransformFeedback();
method public static void glSamplerParameterf(int, int, float);
@@ -38771,6 +38773,7 @@
method public final void conferenceRemoteConnections(android.telecom.RemoteConnection, android.telecom.RemoteConnection);
method public final android.telecom.RemoteConnection createRemoteIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
method public final android.telecom.RemoteConnection createRemoteOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+ method public final java.util.Collection<android.telecom.Conference> getAllConferences();
method public final java.util.Collection<android.telecom.Connection> getAllConnections();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConference(android.telecom.Connection, android.telecom.Connection);
@@ -39223,10 +39226,12 @@
package android.telephony {
public class CarrierConfigManager {
+ method public android.os.PersistableBundle getConfig(int);
method public android.os.PersistableBundle getConfig();
- method public android.os.PersistableBundle getConfigForSubId(int);
+ method public deprecated android.os.PersistableBundle getConfigForSubId(int);
method public static android.os.PersistableBundle getDefaultConfig();
- method public void notifyConfigChangedForSubId(int);
+ method public void notifyConfigChanged(int);
+ method public deprecated void notifyConfigChangedForSubId(int);
method public void updateConfigForPhoneId(int, java.lang.String);
field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
field public static final java.lang.String BOOL_ALLOW_EMERGENCY_VIDEO_CALLS = "bool_allow_emergency_video_calls";
diff --git a/api/test-current.txt b/api/test-current.txt
index 0b1c208..4b62e28 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -23006,6 +23006,7 @@
field public static final java.lang.String ACTION_BLOCKED_RATINGS_CHANGED = "android.media.tv.action.BLOCKED_RATINGS_CHANGED";
field public static final java.lang.String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED = "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
field public static final java.lang.String ACTION_QUERY_CONTENT_RATING_SYSTEMS = "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
+ field public static final java.lang.String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
field public static final int INPUT_STATE_CONNECTED = 0; // 0x0
field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1
field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2
@@ -26897,6 +26898,7 @@
method public static void glProgramBinary(int, int, java.nio.Buffer, int);
method public static void glProgramParameteri(int, int, int);
method public static void glReadBuffer(int);
+ method public static void glReadPixels(int, int, int, int, int, int, int);
method public static void glRenderbufferStorageMultisample(int, int, int, int, int);
method public static void glResumeTransformFeedback();
method public static void glSamplerParameterf(int, int, float);
@@ -36155,6 +36157,7 @@
method public final void conferenceRemoteConnections(android.telecom.RemoteConnection, android.telecom.RemoteConnection);
method public final android.telecom.RemoteConnection createRemoteIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
method public final android.telecom.RemoteConnection createRemoteOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest);
+ method public final java.util.Collection<android.telecom.Conference> getAllConferences();
method public final java.util.Collection<android.telecom.Connection> getAllConnections();
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConference(android.telecom.Connection, android.telecom.Connection);
@@ -36523,9 +36526,11 @@
package android.telephony {
public class CarrierConfigManager {
+ method public android.os.PersistableBundle getConfig(int);
method public android.os.PersistableBundle getConfig();
- method public android.os.PersistableBundle getConfigForSubId(int);
- method public void notifyConfigChangedForSubId(int);
+ method public deprecated android.os.PersistableBundle getConfigForSubId(int);
+ method public void notifyConfigChanged(int);
+ method public deprecated void notifyConfigChangedForSubId(int);
field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
field public static final java.lang.String BOOL_ALLOW_EMERGENCY_VIDEO_CALLS = "bool_allow_emergency_video_calls";
field public static final java.lang.String BOOL_ALLOW_VIDEO_PAUSE = "bool_allow_video_pause";
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 24449d4..4025553 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -1490,7 +1490,7 @@
System.err.println(" -i: specify the installer package name");
System.err.println(" -s: install application on sdcard");
System.err.println(" -f: install application on internal flash");
- System.err.println(" -d: allow version code downgrade");
+ System.err.println(" -d: allow version code downgrade (debuggable packages only)");
System.err.println(" -p: partial application install");
System.err.println(" -g: grant all runtime permissions");
System.err.println(" -S: size in bytes of entire session");
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 6067577..da49b64 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -426,7 +426,9 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
+ final int attrIndex = index;
index *= AssetManager.STYLE_NUM_ENTRIES;
+
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
@@ -444,13 +446,13 @@
return defValue;
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
- getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
+ getValueAt(index, value);
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + index + ": " + value);
+ "Failed to resolve attribute at index " + attrIndex + ": " + value);
}
- throw new UnsupportedOperationException("Can't convert to color: type=0x"
- + Integer.toHexString(type));
+ throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ + " to color: type=0x" + Integer.toHexString(type));
}
/**
@@ -541,7 +543,9 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
+ final int attrIndex = index;
index *= AssetManager.STYLE_NUM_ENTRIES;
+
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
@@ -551,13 +555,13 @@
return data[index+AssetManager.STYLE_DATA];
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
- getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
+ getValueAt(index, value);
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + index + ": " + value);
+ "Failed to resolve attribute at index " + attrIndex + ": " + value);
}
- throw new UnsupportedOperationException("Can't convert to integer: type=0x"
- + Integer.toHexString(type));
+ throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ + " to integer: type=0x" + Integer.toHexString(type));
}
/**
@@ -587,7 +591,9 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
+ final int attrIndex = index;
index *= AssetManager.STYLE_NUM_ENTRIES;
+
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
@@ -597,13 +603,13 @@
data[index + AssetManager.STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
- getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
+ getValueAt(index, value);
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + index + ": " + value);
+ "Failed to resolve attribute at index " + attrIndex + ": " + value);
}
- throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
- + Integer.toHexString(type));
+ throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ + " to dimension: type=0x" + Integer.toHexString(type));
}
/**
@@ -634,7 +640,9 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
+ final int attrIndex = index;
index *= AssetManager.STYLE_NUM_ENTRIES;
+
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
@@ -644,13 +652,13 @@
data[index + AssetManager.STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
- getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
+ getValueAt(index, value);
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + index + ": " + value);
+ "Failed to resolve attribute at index " + attrIndex + ": " + value);
}
- throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
- + Integer.toHexString(type));
+ throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ + " to dimension: type=0x" + Integer.toHexString(type));
}
/**
@@ -682,7 +690,9 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
+ final int attrIndex = index;
index *= AssetManager.STYLE_NUM_ENTRIES;
+
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
@@ -692,13 +702,13 @@
data[index+AssetManager.STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
- getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
+ getValueAt(index, value);
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + index + ": " + value);
+ "Failed to resolve attribute at index " + attrIndex + ": " + value);
}
- throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
- + Integer.toHexString(type));
+ throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ + " to dimension: type=0x" + Integer.toHexString(type));
}
/**
@@ -724,7 +734,9 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
+ final int attrIndex = index;
index *= AssetManager.STYLE_NUM_ENTRIES;
+
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type >= TypedValue.TYPE_FIRST_INT
@@ -735,9 +747,9 @@
data[index+AssetManager.STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
- getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
+ getValueAt(index, value);
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + index + ": " + value);
+ "Failed to resolve attribute at index " + attrIndex + ": " + value);
}
throw new UnsupportedOperationException(getPositionDescription()
@@ -800,7 +812,9 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
+ final int attrIndex = index;
index *= AssetManager.STYLE_NUM_ENTRIES;
+
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
@@ -810,13 +824,13 @@
data[index+AssetManager.STYLE_DATA], base, pbase);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
- getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value);
+ getValueAt(index, value);
throw new UnsupportedOperationException(
- "Failed to resolve attribute at index " + index + ": " + value);
+ "Failed to resolve attribute at index " + attrIndex + ": " + value);
}
- throw new UnsupportedOperationException("Can't convert to fraction: type=0x"
- + Integer.toHexString(type));
+ throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ + " to fraction: type=0x" + Integer.toHexString(type));
}
/**
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index 367c9fb..e79dfca 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -437,6 +437,7 @@
if (handled) {
adjustMetaAfterKeypress(content);
+ return true;
}
return super.onKeyDown(view, content, keyCode, event);
@@ -470,4 +471,3 @@
return true;
}
}
-
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 4c81d1a..54b3932 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -1632,7 +1632,6 @@
final PopupDecorView decorView = mDecorView;
final View contentView = mContentView;
- final OnDismissListener dismissListener = mOnDismissListener;
final ViewGroup contentHolder;
final ViewParent contentParent = contentView.getParent();
@@ -1676,16 +1675,19 @@
new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
- dismissImmediate(decorView, contentHolder,
- contentView, dismissListener);
+ dismissImmediate(decorView, contentHolder, contentView);
}
});
} else {
- dismissImmediate(decorView, contentHolder, contentView, dismissListener);
+ dismissImmediate(decorView, contentHolder, contentView);
}
// Clears the anchor view.
unregisterForViewTreeChanges();
+
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss();
+ }
}
/**
@@ -1727,8 +1729,7 @@
* Removes the popup from the window manager and tears down the supporting
* view hierarchy, if necessary.
*/
- private void dismissImmediate(View decorView, ViewGroup contentHolder,
- View contentView, OnDismissListener listener) {
+ private void dismissImmediate(View decorView, ViewGroup contentHolder, View contentView) {
// If this method gets called and the decor view doesn't have a parent,
// then it was either never added or was already removed. That should
// never happen, but it's worth checking to avoid potential crashes.
@@ -1745,10 +1746,6 @@
mDecorView = null;
mBackgroundView = null;
mIsTransitioningToDismiss = false;
-
- if (mOnDismissListener != null) {
- mOnDismissListener.onDismiss();
- }
}
/**
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 914ffdf..1cccfae 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -454,12 +454,19 @@
public void onClick(View v) {
// Insure that this view is a child of an AdapterView
View parent = (View) v.getParent();
+ // Break the for loop on the first encounter of:
+ // 1) an AdapterView,
+ // 2) an AppWidgetHostView that is not a RemoteViewsFrameLayout, or
+ // 3) a null parent.
+ // 2) and 3) are unexpected and catch the case where a child is not
+ // correctly parented in an AdapterView.
while (parent != null && !(parent instanceof AdapterView<?>)
- && !(parent instanceof AppWidgetHostView)) {
+ && !((parent instanceof AppWidgetHostView) &&
+ !(parent instanceof RemoteViewsAdapter.RemoteViewsFrameLayout))) {
parent = (View) parent.getParent();
}
- if (parent instanceof AppWidgetHostView || parent == null) {
+ if (!(parent instanceof AdapterView<?>)) {
// Somehow they've managed to get this far without having
// and AdapterView as a parent.
Log.e(LOG_TAG, "Collection item doesn't have AdapterView parent");
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 8278c5a..10abbab 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -288,7 +288,7 @@
* A FrameLayout which contains a loading view, and manages the re/applying of RemoteViews when
* they are loaded.
*/
- private static class RemoteViewsFrameLayout extends AppWidgetHostView {
+ static class RemoteViewsFrameLayout extends AppWidgetHostView {
private final FixedSizeRemoteViewsCache mCache;
public RemoteViewsFrameLayout(Context context, FixedSizeRemoteViewsCache cache) {
diff --git a/core/jni/android_opengl_GLES30.cpp b/core/jni/android_opengl_GLES30.cpp
index c9b5af7..2d69eaa 100644
--- a/core/jni/android_opengl_GLES30.cpp
+++ b/core/jni/android_opengl_GLES30.cpp
@@ -5155,6 +5155,21 @@
}
}
+/* void glReadPixels ( GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint offset ) */
+static void
+android_glReadPixels__IIIIIII
+ (JNIEnv *_env, jobject _this, jint x, jint y, jint width, jint height, jint format, jint type, jint offset) {
+ glReadPixels(
+ (GLint)x,
+ (GLint)y,
+ (GLsizei)width,
+ (GLsizei)height,
+ (GLenum)format,
+ (GLenum)type,
+ reinterpret_cast<GLvoid *>(offset)
+ );
+}
+
static const char *classPathName = "android/opengl/GLES30";
static const JNINativeMethod methods[] = {
@@ -5320,6 +5335,7 @@
{"glTexStorage3D", "(IIIIII)V", (void *) android_glTexStorage3D__IIIIII },
{"glGetInternalformativ", "(IIII[II)V", (void *) android_glGetInternalformativ__IIII_3II },
{"glGetInternalformativ", "(IIIILjava/nio/IntBuffer;)V", (void *) android_glGetInternalformativ__IIIILjava_nio_IntBuffer_2 },
+{"glReadPixels", "(IIIIIII)V", (void *) android_glReadPixels__IIIIIII },
};
int register_android_opengl_jni_GLES30(JNIEnv *_env)
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index a3a3112..ae19150 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -25,7 +25,7 @@
<bool name="config_defaultWindowFeatureOptionsPanel">false</bool>
<!-- Default bounds [left top right bottom] on screen for picture-in-picture windows. -->
- <string translatable="false" name="config_defaultPictureInPictureBounds">"1328 54 1808 324"</string>
+ <string translatable="false" name="config_defaultPictureInPictureBounds">"1420 100 1820 325"</string>
<!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, when the PIP
is located in center. -->
@@ -34,5 +34,4 @@
<!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows,
when the PIP is shown with Recents. -->
<string translatable="false" name="config_pictureInPictureBoundsInRecents">"1480 123 1760 303"</string>
-
</resources>
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index d05c66a..3b0e7e8 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -898,19 +898,19 @@
* @param res the resources used to inflate density-dependent values
*/
final void updateDensity(Resources res) {
- if (mSourceRes != null) {
+ if (res != null) {
mSourceRes = res;
- }
- // The density may have changed since the last update (if any). Any
- // dimension-type attributes will need their default values scaled.
- final int targetDensity = Drawable.resolveDensity(res, mDensity);
- final int sourceDensity = mDensity;
- mDensity = targetDensity;
+ // The density may have changed since the last update (if any). Any
+ // dimension-type attributes will need their default values scaled.
+ final int targetDensity = Drawable.resolveDensity(res, mDensity);
+ final int sourceDensity = mDensity;
+ mDensity = targetDensity;
- if (sourceDensity != targetDensity) {
- mCheckedConstantSize = false;
- mCheckedPadding = false;
+ if (sourceDensity != targetDensity) {
+ mCheckedConstantSize = false;
+ mCheckedPadding = false;
+ }
}
}
diff --git a/libs/hwui/renderstate/MeshState.cpp b/libs/hwui/renderstate/MeshState.cpp
index 03cb5ce..b575c69 100644
--- a/libs/hwui/renderstate/MeshState.cpp
+++ b/libs/hwui/renderstate/MeshState.cpp
@@ -34,7 +34,6 @@
glGenBuffers(1, &mUnitQuadBuffer);
glBindBuffer(GL_ARRAY_BUFFER, mUnitQuadBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(kUnitQuadVertices), kUnitQuadVertices, GL_STATIC_DRAW);
-
mCurrentBuffer = mUnitQuadBuffer;
uint16_t regionIndices[kMaxNumberOfQuads * 6];
@@ -78,26 +77,18 @@
// Buffer Objects
///////////////////////////////////////////////////////////////////////////////
-bool MeshState::bindMeshBuffer() {
- return bindMeshBuffer(mUnitQuadBuffer);
-}
-
-bool MeshState::bindMeshBuffer(GLuint buffer) {
- if (!buffer) buffer = mUnitQuadBuffer;
- return bindMeshBufferInternal(buffer);
-}
-
-bool MeshState::unbindMeshBuffer() {
- return bindMeshBufferInternal(0);
-}
-
-bool MeshState::bindMeshBufferInternal(GLuint buffer) {
+void MeshState::bindMeshBuffer(GLuint buffer) {
if (mCurrentBuffer != buffer) {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
mCurrentBuffer = buffer;
- return true;
+
+ // buffer has changed, so invalidate cached vertex pos/texcoord pointers
+ resetVertexPointers();
}
- return false;
+}
+
+void MeshState::unbindMeshBuffer() {
+ return bindMeshBuffer(0);
}
void MeshState::genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size,
@@ -122,16 +113,22 @@
// Vertices
///////////////////////////////////////////////////////////////////////////////
-void MeshState::bindPositionVertexPointer(bool force, const GLvoid* vertices, GLsizei stride) {
- if (force || vertices != mCurrentPositionPointer || stride != mCurrentPositionStride) {
+void MeshState::bindPositionVertexPointer(const GLvoid* vertices, GLsizei stride) {
+ // update pos coords if !current vbo, since vertices may point into mutable memory (e.g. stack)
+ if (mCurrentBuffer == 0
+ || vertices != mCurrentPositionPointer
+ || stride != mCurrentPositionStride) {
glVertexAttribPointer(Program::kBindingPosition, 2, GL_FLOAT, GL_FALSE, stride, vertices);
mCurrentPositionPointer = vertices;
mCurrentPositionStride = stride;
}
}
-void MeshState::bindTexCoordsVertexPointer(bool force, const GLvoid* vertices, GLsizei stride) {
- if (force || vertices != mCurrentTexCoordsPointer || stride != mCurrentTexCoordsStride) {
+void MeshState::bindTexCoordsVertexPointer(const GLvoid* vertices, GLsizei stride) {
+ // update tex coords if !current vbo, since vertices may point into mutable memory (e.g. stack)
+ if (mCurrentBuffer == 0
+ || vertices != mCurrentTexCoordsPointer
+ || stride != mCurrentTexCoordsStride) {
glVertexAttribPointer(Program::kBindingTexCoords, 2, GL_FLOAT, GL_FALSE, stride, vertices);
mCurrentTexCoordsPointer = vertices;
mCurrentTexCoordsStride = stride;
@@ -143,10 +140,6 @@
mCurrentTexCoordsPointer = this;
}
-void MeshState::resetTexCoordsVertexPointer() {
- mCurrentTexCoordsPointer = this;
-}
-
void MeshState::enableTexCoordsVertexArray() {
if (!mTexCoordsArrayEnabled) {
glEnableVertexAttribArray(Program::kBindingTexCoords);
@@ -166,26 +159,18 @@
// Indices
///////////////////////////////////////////////////////////////////////////////
-bool MeshState::bindIndicesBufferInternal(const GLuint buffer) {
+void MeshState::bindIndicesBuffer(const GLuint buffer) {
if (mCurrentIndicesBuffer != buffer) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
mCurrentIndicesBuffer = buffer;
- return true;
}
- return false;
}
-bool MeshState::bindQuadIndicesBuffer() {
- return bindIndicesBufferInternal(mQuadListIndices);
-}
-
-bool MeshState::unbindIndicesBuffer() {
+void MeshState::unbindIndicesBuffer() {
if (mCurrentIndicesBuffer) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
mCurrentIndicesBuffer = 0;
- return true;
}
- return false;
}
} /* namespace uirenderer */
diff --git a/libs/hwui/renderstate/MeshState.h b/libs/hwui/renderstate/MeshState.h
index 6c0fb78..dd68468 100644
--- a/libs/hwui/renderstate/MeshState.h
+++ b/libs/hwui/renderstate/MeshState.h
@@ -60,20 +60,16 @@
///////////////////////////////////////////////////////////////////////////////
// Buffer objects
///////////////////////////////////////////////////////////////////////////////
- /**
- * Binds the VBO used to render simple textured quads.
- */
- bool bindMeshBuffer();
/**
* Binds the specified VBO if needed. If buffer == 0, binds default simple textured quad.
*/
- bool bindMeshBuffer(GLuint buffer);
+ void bindMeshBuffer(GLuint buffer);
/**
- * Unbinds the VBO used to render simple textured quads.
+ * Unbinds the current VBO if active.
*/
- bool unbindMeshBuffer();
+ void unbindMeshBuffer();
void genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size, const void* data, GLenum usage);
void deleteMeshBuffer(GLuint);
@@ -85,21 +81,20 @@
* Binds an attrib to the specified float vertex pointer.
* Assumes a stride of gTextureVertexStride and a size of 2.
*/
- void bindPositionVertexPointer(bool force, const GLvoid* vertices,
+ void bindPositionVertexPointer(const GLvoid* vertices,
GLsizei stride = kTextureVertexStride);
/**
* Binds an attrib to the specified float vertex pointer.
* Assumes a stride of gTextureVertexStride and a size of 2.
*/
- void bindTexCoordsVertexPointer(bool force, const GLvoid* vertices,
+ void bindTexCoordsVertexPointer(const GLvoid* vertices,
GLsizei stride = kTextureVertexStride);
/**
* Resets the vertex pointers.
*/
void resetVertexPointers();
- void resetTexCoordsVertexPointer();
void enableTexCoordsVertexArray();
void disableTexCoordsVertexArray();
@@ -107,12 +102,8 @@
///////////////////////////////////////////////////////////////////////////////
// Indices
///////////////////////////////////////////////////////////////////////////////
- /**
- * Binds a global indices buffer that can draw up to
- * gMaxNumberOfQuads quads.
- */
- bool bindQuadIndicesBuffer();
- bool unbindIndicesBuffer();
+ void bindIndicesBuffer(const GLuint buffer);
+ void unbindIndicesBuffer();
///////////////////////////////////////////////////////////////////////////////
// Getters - for use in Glop building
@@ -121,8 +112,6 @@
GLuint getQuadListIBO() { return mQuadListIndices; }
private:
MeshState();
- bool bindMeshBufferInternal(const GLuint buffer);
- bool bindIndicesBufferInternal(const GLuint buffer);
GLuint mUnitQuadBuffer;
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index e535f2f..ea4391b 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -292,28 +292,27 @@
// ---------- Mesh setup ----------
// --------------------------------
// vertices
- const bool force = meshState().bindMeshBufferInternal(vertices.bufferObject)
- || (vertices.position != nullptr);
- meshState().bindPositionVertexPointer(force, vertices.position, vertices.stride);
+ meshState().bindMeshBuffer(vertices.bufferObject);
+ meshState().bindPositionVertexPointer(vertices.position, vertices.stride);
// indices
- meshState().bindIndicesBufferInternal(indices.bufferObject);
+ meshState().bindIndicesBuffer(indices.bufferObject);
if (vertices.attribFlags & VertexAttribFlags::TextureCoord) {
const Glop::Fill::TextureData& texture = fill.texture;
// texture always takes slot 0, shader samplers increment from there
mCaches->textureState().activateTexture(0);
+ mCaches->textureState().bindTexture(texture.target, texture.texture->id());
if (texture.clamp != GL_INVALID_ENUM) {
- texture.texture->setWrap(texture.clamp, true, false, texture.target);
+ texture.texture->setWrap(texture.clamp, false, false, texture.target);
}
if (texture.filter != GL_INVALID_ENUM) {
- texture.texture->setFilter(texture.filter, true, false, texture.target);
+ texture.texture->setFilter(texture.filter, false, false, texture.target);
}
- mCaches->textureState().bindTexture(texture.target, texture.texture->id());
meshState().enableTexCoordsVertexArray();
- meshState().bindTexCoordsVertexPointer(force, vertices.texCoord, vertices.stride);
+ meshState().bindTexCoordsVertexPointer(vertices.texCoord, vertices.stride);
if (texture.textureTransform) {
glUniformMatrix4fv(fill.program->getUniform("mainTextureTransform"), 1,
@@ -361,11 +360,9 @@
const GLbyte* vertexData = static_cast<const GLbyte*>(vertices.position);
while (elementsCount > 0) {
GLsizei drawCount = std::min(elementsCount, (GLsizei) kMaxNumberOfQuads * 6);
-
- // rebind pointers without forcing, since initial bind handled above
- meshState().bindPositionVertexPointer(false, vertexData, vertices.stride);
+ meshState().bindPositionVertexPointer(vertexData, vertices.stride);
if (vertices.attribFlags & VertexAttribFlags::TextureCoord) {
- meshState().bindTexCoordsVertexPointer(false,
+ meshState().bindTexCoordsVertexPointer(
vertexData + kMeshTextureOffset, vertices.stride);
}
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index bd0019f..3affee5c0 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -50,4 +50,6 @@
void setPlaybackToLocal(in AudioAttributes attributes);
void setPlaybackToRemote(int control, int max);
void setCurrentVolume(int currentVolume);
+
+ String getCallingPackage();
}
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 3b1b6c8..9073077 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -512,6 +512,22 @@
}
}
+ /**
+ * Returns the name of the package that sent the last media button, transport control, or
+ * command from controllers and the system. This is only valid while in a request callback, such
+ * as {@link Callback#onPlay}.
+ *
+ * @hide
+ */
+ public String getCallingPackage() {
+ try {
+ return mBinder.getCallingPackage();
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Dead object in getCallingPackage.", e);
+ }
+ return null;
+ }
+
private void dispatchPrepare() {
postToCallback(CallbackMessageHandler.MSG_PREPARE);
}
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index b8a1374..ec65ffe 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -105,7 +105,8 @@
public static final int TYPE_DISPLAY_PORT = 1008;
/**
- * The ID of the TV input to provide to the setup activity and settings activity.
+ * Used as a String extra field in setup intents created by {@link #createSetupIntent()} to
+ * supply the ID of a specific TV input to set up.
*/
public static final String EXTRA_INPUT_ID = "android.media.tv.extra.INPUT_ID";
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 7089c00..2703b1a 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -266,6 +266,14 @@
public static final String META_DATA_CONTENT_RATING_SYSTEMS =
"android.media.tv.metadata.CONTENT_RATING_SYSTEMS";
+ /**
+ * Activity action to set up channel sources i.e. TV inputs of type
+ * {@link TvInputInfo#TYPE_TUNER}. When invoked, the system will display an appropriate UI for
+ * the user to initiate the individual setup flow provided by
+ * {@link android.R.attr#setupActivity} of each TV input service.
+ */
+ public static final String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
+
private final ITvInputManager mService;
private final Object mLock = new Object();
diff --git a/opengl/java/android/opengl/GLES30.java b/opengl/java/android/opengl/GLES30.java
index 342ffa4..9c3b505 100644
--- a/opengl/java/android/opengl/GLES30.java
+++ b/opengl/java/android/opengl/GLES30.java
@@ -1791,4 +1791,16 @@
java.nio.IntBuffer params
);
+ // C function void glReadPixels ( GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint offset )
+
+ public static native void glReadPixels(
+ int x,
+ int y,
+ int width,
+ int height,
+ int format,
+ int type,
+ int offset
+ );
+
}
diff --git a/packages/DocumentsUI/Android.mk b/packages/DocumentsUI/Android.mk
index 1a4e3eb..e1650e1 100644
--- a/packages/DocumentsUI/Android.mk
+++ b/packages/DocumentsUI/Android.mk
@@ -8,6 +8,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
# The design lib requires that the client package use appcompat themes.
LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-appcompat
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v13
# Supplies material design components, e.g. Snackbar.
LOCAL_STATIC_JAVA_LIBRARIES += android-support-design
LOCAL_STATIC_JAVA_LIBRARIES += android-support-v7-recyclerview
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index cb6cf8d..90c2d1b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -52,6 +52,7 @@
import android.provider.DocumentsContract.Document;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
+import android.support.v13.view.DragStartHelper;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
import android.support.v7.widget.RecyclerView;
@@ -72,7 +73,6 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewParent;
import android.widget.ImageView;
import android.widget.TextView;
@@ -258,7 +258,8 @@
}
mRecView.setLayoutManager(mLayout);
- mGestureDetector = new ListeningGestureDetector(this.getContext(), new GestureListener());
+ mGestureDetector =
+ new ListeningGestureDetector(this.getContext(), mDragHelper, new GestureListener());
mRecView.addOnItemTouchListener(mGestureDetector);
@@ -1051,7 +1052,7 @@
view.setOnDragListener(mOnDragListener);
}
- view.setOnLongClickListener(mLongClickListener);
+ view.setOnLongClickListener(mDragHelper);
}
private View.OnDragListener mOnDragListener = new View.OnDragListener() {
@@ -1062,11 +1063,15 @@
// TODO: Check if the event contains droppable data.
return true;
- // TODO: Highlight potential drop target directory?
// TODO: Expand drop target directory on hover?
case DragEvent.ACTION_DRAG_ENTERED:
- case DragEvent.ACTION_DRAG_LOCATION:
+ setDropTargetHighlight(v, true);
+ return true;
case DragEvent.ACTION_DRAG_EXITED:
+ setDropTargetHighlight(v, false);
+ return true;
+
+ case DragEvent.ACTION_DRAG_LOCATION:
case DragEvent.ACTION_DRAG_ENDED:
return true;
@@ -1080,10 +1085,24 @@
// TODO: Do not drop into the directory where the documents came from.
}
copyFromClipData(event.getClipData(), dstDir);
+ // Clean up the UI.
+ setDropTargetHighlight(v, false);
+ mSelectionManager.clearSelection();
return true;
}
return false;
}
+
+ private void setDropTargetHighlight(View v, boolean highlight) {
+ // Note: use exact comparison - this code is searching for views which are children of
+ // the RecyclerView instance in the UI.
+ if (v.getParent() == mRecView) {
+ RecyclerView.ViewHolder vh = mRecView.getChildViewHolder(v);
+ if (vh instanceof DocumentHolder) {
+ ((DocumentHolder) vh).setHighlighted(highlight);
+ }
+ }
+ }
};
/**
@@ -1109,21 +1128,14 @@
* a document item view.
*/
private String getModelId(View view) {
- while (true) {
- if (view.getLayoutParams() instanceof RecyclerView.LayoutParams) {
- RecyclerView.ViewHolder vh = mRecView.getChildViewHolder(view);
- if (vh instanceof DocumentHolder) {
- return ((DocumentHolder) vh).modelId;
- } else {
- return null;
- }
+ View itemView = mRecView.findContainingItemView(view);
+ if (itemView != null) {
+ RecyclerView.ViewHolder vh = mRecView.getChildViewHolder(itemView);
+ if (vh instanceof DocumentHolder) {
+ return ((DocumentHolder) vh).modelId;
}
- ViewParent parent = view.getParent();
- if (parent == null || !(parent instanceof View)) {
- return null;
- }
- view = (View) parent;
}
+ return null;
}
private List<DocumentInfo> getDraggableDocuments(View currentItemView) {
@@ -1304,10 +1316,10 @@
}
}
- private View.OnLongClickListener mLongClickListener = new View.OnLongClickListener() {
+ private DragStartHelper mDragHelper = new DragStartHelper(null) {
@Override
- public boolean onLongClick(View v) {
- if (mGestureDetector.mouseSpawnedLastEvent()) {
+ protected boolean onDragStart(View v) {
+ if (isSelected(getModelId(v))) {
List<DocumentInfo> docs = getDraggableDocuments(v);
if (docs.isEmpty()) {
return false;
@@ -1333,9 +1345,12 @@
implements OnItemTouchListener {
private int mLastTool = -1;
+ private DragStartHelper mDragHelper;
- public ListeningGestureDetector(Context context, GestureListener listener) {
+ public ListeningGestureDetector(
+ Context context, DragStartHelper dragHelper, GestureListener listener) {
super(context, listener);
+ mDragHelper = dragHelper;
setOnDoubleTapListener(listener);
}
@@ -1350,12 +1365,27 @@
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
mLastTool = e.getToolType(0);
- onTouchEvent(e); // bounce this forward to our detecty heart
+
+ // Detect drag events. When a drag is detected, intercept the rest of the gesture.
+ View itemView = rv.findChildViewUnder(e.getX(), e.getY());
+ if (itemView != null && mDragHelper.handleTouch(itemView, e)) {
+ return true;
+ }
+ // Forward unhandled events to the GestureDetector.
+ onTouchEvent(e);
+
return false;
}
@Override
- public void onTouchEvent(RecyclerView rv, MotionEvent e) {}
+ public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+ View itemView = rv.findChildViewUnder(e.getX(), e.getY());
+ mDragHelper.handleTouch(itemView, e);
+ // Note: even though this event is being handled as part of a drag gesture, continue
+ // forwarding to the GestureDetector. The detector needs to see the entire cluster of
+ // events in order to properly interpret gestures.
+ onTouchEvent(e);
+ }
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
index 1bfc6e9..2967a90 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
@@ -75,11 +75,25 @@
*/
public abstract void bind(Cursor cursor, String modelId, State state);
+ /**
+ * Makes the associated item view appear selected. Note that this merely affects the appearance
+ * of the view, it doesn't actually select the item.
+ *
+ * @param selected
+ */
public void setSelected(boolean selected) {
itemView.setActivated(selected);
itemView.setBackgroundColor(selected ? mSelectedItemColor : mDefaultItemColor);
}
+ /**
+ * Highlights the associated item view.
+ * @param highlighted
+ */
+ public void setHighlighted(boolean highlighted) {
+ itemView.setBackgroundColor(highlighted ? mSelectedItemColor : mDefaultItemColor);
+ }
+
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// Event listener should always be set.
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
index 90b5c09..712dbcb 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -16,6 +16,8 @@
package com.android.mtp;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
import android.content.ContentResolver;
import android.database.Cursor;
import android.mtp.MtpObjectInfo;
@@ -25,6 +27,8 @@
import android.provider.DocumentsContract;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
@@ -38,23 +42,135 @@
* background thread to load the rest documents and caches its result for next requests.
* TODO: Rename this class to ObjectInfoLoader
*/
-class DocumentLoader {
+class DocumentLoader implements AutoCloseable {
static final int NUM_INITIAL_ENTRIES = 10;
static final int NUM_LOADING_ENTRIES = 20;
static final int NOTIFY_PERIOD_MS = 500;
+ private final int mDeviceId;
private final MtpManager mMtpManager;
private final ContentResolver mResolver;
private final MtpDatabase mDatabase;
private final TaskList mTaskList = new TaskList();
- private boolean mHasBackgroundThread = false;
+ private Thread mBackgroundThread;
- DocumentLoader(MtpManager mtpManager, ContentResolver resolver, MtpDatabase database) {
+ DocumentLoader(int deviceId, MtpManager mtpManager, ContentResolver resolver,
+ MtpDatabase database) {
+ mDeviceId = deviceId;
mMtpManager = mtpManager;
mResolver = resolver;
mDatabase = database;
}
+ /**
+ * Queries the child documents of given parent.
+ * It loads the first NUM_INITIAL_ENTRIES of object info, then launches the background thread
+ * to load the rest.
+ */
+ synchronized Cursor queryChildDocuments(String[] columnNames, Identifier parent)
+ throws IOException {
+ Preconditions.checkArgument(parent.mDeviceId == mDeviceId);
+ LoaderTask task = mTaskList.findTask(parent);
+ if (task == null) {
+ if (parent.mDocumentId == null) {
+ throw new FileNotFoundException("Parent not found.");
+ }
+ // TODO: Handle nit race around here.
+ // 1. getObjectHandles.
+ // 2. putNewDocument.
+ // 3. startAddingChildDocuemnts.
+ // 4. stopAddingChildDocuments - It removes the new document added at the step 2,
+ // because it is not updated between start/stopAddingChildDocuments.
+ task = LoaderTask.create(mDatabase, mMtpManager, parent);
+ task.fillDocuments(loadDocuments(
+ mMtpManager,
+ parent.mDeviceId,
+ task.getUnloadedObjectHandles(NUM_INITIAL_ENTRIES)));
+ } else {
+ // Once remove the existing task in order to add it to the head of the list.
+ mTaskList.remove(task);
+ }
+
+ mTaskList.addFirst(task);
+ if (task.getState() == LoaderTask.STATE_LOADING) {
+ resume();
+ }
+ return task.createCursor(mResolver, columnNames);
+ }
+
+ /**
+ * Resumes a background thread.
+ */
+ synchronized void resume() {
+ if (mBackgroundThread == null) {
+ mBackgroundThread = new BackgroundLoaderThread();
+ mBackgroundThread.start();
+ }
+ }
+
+ /**
+ * Obtains next task to be run in background thread, or release the reference to background
+ * thread.
+ *
+ * Worker thread that receives null task needs to exit.
+ */
+ @WorkerThread
+ synchronized @Nullable LoaderTask getNextTaskOrReleaseBackgroundThread() {
+ Preconditions.checkState(mBackgroundThread != null);
+
+ final LoaderTask task = mTaskList.findRunningTask();
+ if (task != null) {
+ return task;
+ }
+
+ final Identifier identifier = mDatabase.getUnmappedDocumentsParent(mDeviceId);
+ if (identifier != null) {
+ final LoaderTask existingTask = mTaskList.findTask(identifier);
+ if (existingTask != null) {
+ Preconditions.checkState(existingTask.getState() != LoaderTask.STATE_LOADING);
+ mTaskList.remove(existingTask);
+ }
+ try {
+ final LoaderTask newTask = LoaderTask.create(mDatabase, mMtpManager, identifier);
+ mTaskList.addFirst(newTask);
+ return newTask;
+ } catch (IOException exception) {
+ Log.e(MtpDocumentsProvider.TAG, "Failed to create a task for mapping", exception);
+ // Continue to release the background thread.
+ }
+ }
+
+ mBackgroundThread = null;
+ return null;
+ }
+
+ /**
+ * Terminates background thread.
+ */
+ @Override
+ public void close() throws InterruptedException {
+ final Thread thread;
+ synchronized (this) {
+ mTaskList.clear();
+ thread = mBackgroundThread;
+ }
+ if (thread != null) {
+ thread.interrupt();
+ thread.join();
+ }
+ }
+
+ synchronized void clearCompletedTasks() {
+ mTaskList.clearCompletedTasks();
+ }
+
+ synchronized void clearTask(Identifier parentIdentifier) {
+ mTaskList.clearTask(parentIdentifier);
+ }
+
+ /**
+ * Helper method to loads multiple object info.
+ */
private static MtpObjectInfo[] loadDocuments(MtpManager manager, int deviceId, int[] handles)
throws IOException {
final ArrayList<MtpObjectInfo> objects = new ArrayList<>();
@@ -70,78 +186,27 @@
return objects.toArray(new MtpObjectInfo[objects.size()]);
}
- synchronized Cursor queryChildDocuments(String[] columnNames, Identifier parent)
- throws IOException {
- LoaderTask task = mTaskList.findTask(parent);
- if (task == null) {
- if (parent.mDocumentId == null) {
- throw new FileNotFoundException("Parent not found.");
- }
-
- int parentHandle = parent.mObjectHandle;
- // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
- // getObjectHandles if we would like to obtain children under the root.
- if (parent.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
- parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
- }
- // TODO: Handle nit race around here.
- // 1. getObjectHandles.
- // 2. putNewDocument.
- // 3. startAddingChildDocuemnts.
- // 4. stopAddingChildDocuments - It removes the new document added at the step 2,
- // because it is not updated between start/stopAddingChildDocuments.
- task = new LoaderTask(mDatabase, parent, mMtpManager.getObjectHandles(
- parent.mDeviceId, parent.mStorageId, parentHandle));
- task.fillDocuments(loadDocuments(
- mMtpManager,
- parent.mDeviceId,
- task.getUnloadedObjectHandles(NUM_INITIAL_ENTRIES)));
- } else {
- // Once remove the existing task in order to add it to the head of the list.
- mTaskList.remove(task);
- }
-
- mTaskList.addFirst(task);
- if (task.getState() == LoaderTask.STATE_LOADING && !mHasBackgroundThread) {
- mHasBackgroundThread = true;
- new BackgroundLoaderThread().start();
- }
- return task.createCursor(mResolver, columnNames);
- }
-
- synchronized void clearTasks() {
- mTaskList.clear();
- }
-
- synchronized void clearCompletedTasks() {
- mTaskList.clearCompletedTasks();
- }
-
- synchronized void clearTask(Identifier parentIdentifier) {
- mTaskList.clearTask(parentIdentifier);
- }
-
+ /**
+ * Background thread to fetch object info.
+ */
private class BackgroundLoaderThread extends Thread {
+ /**
+ * Finds task that needs to be processed, then loads NUM_LOADING_ENTRIES of object info and
+ * store them to the database. If it does not find a task, exits the thread.
+ */
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- while (true) {
- LoaderTask task;
- int deviceId;
- int[] handles;
- synchronized (DocumentLoader.this) {
- task = mTaskList.findRunningTask();
- if (task == null) {
- mHasBackgroundThread = false;
- return;
- }
- deviceId = task.mIdentifier.mDeviceId;
- handles = task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES);
+ while (!Thread.interrupted()) {
+ final LoaderTask task = getNextTaskOrReleaseBackgroundThread();
+ if (task == null) {
+ return;
}
-
try {
- final MtpObjectInfo[] objectInfos =
- loadDocuments(mMtpManager, deviceId, handles);
+ final MtpObjectInfo[] objectInfos = loadDocuments(
+ mMtpManager,
+ task.mIdentifier.mDeviceId,
+ task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES));
task.fillDocuments(objectInfos);
final boolean shouldNotify =
task.mLastNotified.getTime() <
@@ -157,6 +222,9 @@
}
}
+ /**
+ * Task list that has helper methods to search/clear tasks.
+ */
private static class TaskList extends LinkedList<LoaderTask> {
LoaderTask findTask(Identifier parent) {
for (int i = 0; i < size(); i++) {
@@ -197,6 +265,10 @@
}
}
+ /**
+ * Loader task.
+ * Each task is responsible for fetching child documents for the given parent document.
+ */
private static class LoaderTask {
static final int STATE_LOADING = 0;
static final int STATE_COMPLETED = 1;
@@ -217,6 +289,11 @@
mLastNotified = new Date();
}
+ /**
+ * Returns a cursor that traverses the child document of the parent document handled by the
+ * task.
+ * The returned task may have a EXTRA_LOADING flag.
+ */
Cursor createCursor(ContentResolver resolver, String[] columnNames) throws IOException {
final Bundle extras = new Bundle();
switch (getState()) {
@@ -235,6 +312,9 @@
return cursor;
}
+ /**
+ * Returns a state of the task.
+ */
int getState() {
if (mError != null) {
return STATE_ERROR;
@@ -245,6 +325,9 @@
}
}
+ /**
+ * Obtains object handles that have not been loaded yet.
+ */
int[] getUnloadedObjectHandles(int count) {
return Arrays.copyOfRange(
mObjectHandles,
@@ -252,11 +335,17 @@
Math.min(mNumLoaded + count, mObjectHandles.length));
}
+ /**
+ * Notifies a change of child list of the document.
+ */
void notify(ContentResolver resolver) {
resolver.notifyChange(createUri(), null, false);
mLastNotified = new Date();
}
+ /**
+ * Stores object information into database.
+ */
void fillDocuments(MtpObjectInfo[] objectInfoList) {
if (objectInfoList.length == 0 || getState() != STATE_LOADING) {
return;
@@ -276,6 +365,9 @@
}
}
+ /**
+ * Marks the loading task as error.
+ */
void setError(Exception error) {
final int lastState = getState();
setErrorInternal(error);
@@ -298,5 +390,20 @@
return DocumentsContract.buildChildDocumentsUri(
MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId);
}
+
+ /**
+ * Creates a LoaderTask that loads children of the given document.
+ */
+ static LoaderTask create(MtpDatabase database, MtpManager manager, Identifier parent)
+ throws IOException {
+ int parentHandle = parent.mObjectHandle;
+ // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
+ // getObjectHandles if we would like to obtain children under the root.
+ if (parent.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
+ parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
+ }
+ return new LoaderTask(database, parent, manager.getObjectHandles(
+ parent.mDeviceId, parent.mStorageId, parentHandle));
+ }
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
index 48e49fe..9578e6b 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -410,12 +410,7 @@
return null;
}
try {
- final Identifier identifier = mDatabase.createIdentifier(parentId);
- if (mDatabase.getRowState(parentId) == ROW_STATE_DISCONNECTED) {
- throw new FileNotFoundException(
- "document: " + parentId + " is in disconnected device.");
- }
- return identifier;
+ return mDatabase.createIdentifier(parentId);
} catch (FileNotFoundException error) {
mInMappingIds.remove(parentId);
throw error;
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index ca5c799..e14109a 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -40,6 +40,7 @@
import com.android.internal.util.Preconditions;
import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.Objects;
/**
@@ -406,15 +407,15 @@
COLUMN_STORAGE_ID,
COLUMN_OBJECT_HANDLE,
COLUMN_DOCUMENT_TYPE),
- SELECTION_DOCUMENT_ID,
- strings(documentId),
+ SELECTION_DOCUMENT_ID + " AND " + COLUMN_ROW_STATE + " IN (?, ?)",
+ strings(documentId, ROW_STATE_VALID, ROW_STATE_INVALIDATED),
null,
null,
null,
"1");
try {
if (cursor.getCount() == 0) {
- throw new FileNotFoundException("ID is not found.");
+ throw new FileNotFoundException("ID \"" + documentId + "\" is not found.");
} else {
cursor.moveToNext();
return new Identifier(
@@ -598,6 +599,48 @@
}
}
+ /**
+ * Obtains a document that has already mapped but has unmapped children.
+ * @param deviceId Device to find documents.
+ * @return Identifier of found document or null.
+ */
+ public @Nullable Identifier getUnmappedDocumentsParent(int deviceId) {
+ final String fromClosure =
+ TABLE_DOCUMENTS + " AS child INNER JOIN " +
+ TABLE_DOCUMENTS + " AS parent ON " +
+ "child." + COLUMN_PARENT_DOCUMENT_ID + " = " +
+ "parent." + Document.COLUMN_DOCUMENT_ID;
+ final String whereClosure =
+ "parent." + COLUMN_DEVICE_ID + " = ? AND " +
+ "parent." + COLUMN_ROW_STATE + " IN (?, ?) AND " +
+ "child." + COLUMN_ROW_STATE + " = ?";
+ try (final Cursor cursor = mDatabase.query(
+ fromClosure,
+ strings("parent." + COLUMN_DEVICE_ID,
+ "parent." + COLUMN_STORAGE_ID,
+ "parent." + COLUMN_OBJECT_HANDLE,
+ "parent." + Document.COLUMN_DOCUMENT_ID,
+ "parent." + COLUMN_DOCUMENT_TYPE),
+ whereClosure,
+ strings(deviceId, ROW_STATE_VALID, ROW_STATE_INVALIDATED,
+ ROW_STATE_DISCONNECTED),
+ null,
+ null,
+ null,
+ "1")) {
+ if (cursor.getCount() == 0) {
+ return null;
+ }
+ cursor.moveToNext();
+ return new Identifier(
+ cursor.getInt(0),
+ cursor.getInt(1),
+ cursor.getInt(2),
+ cursor.getString(3),
+ cursor.getInt(4));
+ }
+ }
+
private static class OpenHelper extends SQLiteOpenHelper {
public OpenHelper(Context context, int flags) {
super(context,
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index d329e3c..4849978 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -324,14 +324,18 @@
Log.d(TAG, "Open device " + deviceId);
}
mMtpManager.openDevice(deviceId);
- mDeviceToolkits.put(
- deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
+ final DeviceToolkit toolkit =
+ new DeviceToolkit(deviceId, mMtpManager, mResolver, mDatabase);
+ mDeviceToolkits.put(deviceId, toolkit);
mIntentSender.sendUpdateNotificationIntent();
try {
mRootScanner.resume().await();
} catch (InterruptedException error) {
Log.e(TAG, "openDevice", error);
}
+ // Resume document loader to remap disconnected document ID. Must be invoked after the
+ // root scanner resumes.
+ toolkit.mDocumentLoader.resume();
}
}
@@ -425,7 +429,7 @@
if (DEBUG) {
Log.d(TAG, "Close device " + deviceId);
}
- getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
+ getDeviceToolkit(deviceId).mDocumentLoader.close();
mDeviceToolkits.remove(deviceId);
mMtpManager.closeDevice(deviceId);
if (getOpenedDeviceIds().length == 0) {
@@ -485,9 +489,10 @@
public final PipeManager mPipeManager;
public final DocumentLoader mDocumentLoader;
- public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) {
+ public DeviceToolkit(
+ int deviceId, MtpManager manager, ContentResolver resolver, MtpDatabase database) {
mPipeManager = new PipeManager(database);
- mDocumentLoader = new DocumentLoader(manager, resolver, database);
+ mDocumentLoader = new DocumentLoader(deviceId, manager, resolver, database);
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index 474da07..b75a9e6 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -44,7 +44,7 @@
mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putDeviceDocument(
- new MtpDeviceRecord(1, "Device", null, true, new MtpRoot[0], null, null));
+ new MtpDeviceRecord(0, "Device", null, true, new MtpRoot[0], null, null));
mDatabase.getMapper().stopAddingDocuments(null);
mDatabase.getMapper().startAddingDocuments("1");
@@ -55,11 +55,12 @@
mManager = new BlockableTestMtpManager(getContext());
mResolver = new TestContentResolver();
- mLoader = new DocumentLoader(mManager, mResolver, mDatabase);
+ mLoader = new DocumentLoader(0, mManager, mResolver, mDatabase);
}
@Override
- public void tearDown() {
+ public void tearDown() throws Exception {
+ mLoader.close();
mDatabase.close();
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 5eda9b2b8..884d132 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -22,6 +22,7 @@
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
+import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.system.Os;
import android.system.OsConstants;
@@ -587,6 +588,79 @@
}
}
+ public void testMappingDisconnectedDocuments() throws Exception {
+ setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+ mMtpManager.addValidDevice(new MtpDeviceRecord(
+ 0,
+ "Device A",
+ "device key",
+ true /* unopened */,
+ new MtpRoot[] {
+ new MtpRoot(
+ 0 /* deviceId */,
+ 1 /* storageId */,
+ "Storage A" /* volume description */,
+ 1024 /* free space */,
+ 2048 /* total space */,
+ "" /* no volume identifier */)
+ },
+ null,
+ null));
+
+ final String[] names = strings("Directory A", "Directory B", "Directory C");
+ final int objectHandleOffset = 100;
+ for (int i = 0; i < names.length; i++) {
+ final int parentHandle = i == 0 ?
+ MtpManager.OBJECT_HANDLE_ROOT_CHILDREN : objectHandleOffset + i - 1;
+ final int objectHandle = i + objectHandleOffset;
+ mMtpManager.setObjectHandles(0, 1, parentHandle, new int[] { objectHandle });
+ mMtpManager.setObjectInfo(
+ 0,
+ new MtpObjectInfo.Builder()
+ .setName(names[i])
+ .setObjectHandle(objectHandle)
+ .setFormat(MtpConstants.FORMAT_ASSOCIATION)
+ .setStorageId(1)
+ .build());
+ }
+
+ mProvider.resumeRootScanner();
+ mResolver.waitForNotification(ROOTS_URI, 1);
+
+ final int documentIdOffset = 2;
+ for (int i = 0; i < names.length; i++) {
+ try (final Cursor cursor = mProvider.queryChildDocuments(
+ String.valueOf(documentIdOffset + i),
+ strings(Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME),
+ null)) {
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0));
+ assertEquals(names[i], cursor.getString(1));
+ }
+ }
+
+ mProvider.closeDevice(0);
+ mResolver.waitForNotification(ROOTS_URI, 2);
+
+ mProvider.openDevice(0);
+ mResolver.waitForNotification(ROOTS_URI, 3);
+
+ for (int i = 0; i < names.length; i++) {
+ mResolver.waitForNotification(DocumentsContract.buildChildDocumentsUri(
+ MtpDocumentsProvider.AUTHORITY,
+ String.valueOf(documentIdOffset + i)), 1);
+ try (final Cursor cursor = mProvider.queryChildDocuments(
+ String.valueOf(documentIdOffset + i),
+ strings(Document.COLUMN_DOCUMENT_ID),
+ null)) {
+ assertEquals(1, cursor.getCount());
+ cursor.moveToNext();
+ assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0));
+ }
+ }
+ }
+
private void setupProvider(int flag) {
mDatabase = new MtpDatabase(getContext(), flag);
mProvider = new MtpDocumentsProvider();
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
index 1b46f3c..3043ce8 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java
@@ -86,7 +86,7 @@
@Override
void openDevice(int deviceId) throws IOException {
final MtpDeviceRecord device = mDevices.get(deviceId);
- if (device == null || device.opened) {
+ if (device == null) {
throw new IOException();
}
mDevices.put(
@@ -99,7 +99,7 @@
@Override
void closeDevice(int deviceId) throws IOException {
final MtpDeviceRecord device = mDevices.get(deviceId);
- if (device == null || !device.opened) {
+ if (device == null) {
throw new IOException();
}
mDevices.put(
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 1c032fa..a578055 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -18,6 +18,7 @@
import android.annotation.LayoutRes;
import android.annotation.Nullable;
import android.app.Activity;
+import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -235,20 +236,24 @@
Intent.FLAG_ACTIVITY_CLEAR_TASK));
return true;
}
- int numUserHandles = tile.userHandle.size();
- if (numUserHandles > 1) {
- ProfileSelectDialog.show(getFragmentManager(), tile);
- return false;
- } else if (numUserHandles == 1) {
- // Show menu on top level items.
- tile.intent.putExtra(EXTRA_SHOW_MENU, true);
- tile.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- startActivityAsUser(tile.intent, tile.userHandle.get(0));
- } else {
- // Show menu on top level items.
- tile.intent.putExtra(EXTRA_SHOW_MENU, true);
- tile.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- startActivity(tile.intent);
+ try {
+ int numUserHandles = tile.userHandle.size();
+ if (numUserHandles > 1) {
+ ProfileSelectDialog.show(getFragmentManager(), tile);
+ return false;
+ } else if (numUserHandles == 1) {
+ // Show menu on top level items.
+ tile.intent.putExtra(EXTRA_SHOW_MENU, true);
+ tile.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivityAsUser(tile.intent, tile.userHandle.get(0));
+ } else {
+ // Show menu on top level items.
+ tile.intent.putExtra(EXTRA_SHOW_MENU, true);
+ tile.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(tile.intent);
+ }
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Couldn't find tile " + tile.intent, e);
}
return true;
}
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 20aca0e..85d127c 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -302,8 +302,7 @@
}
final String action = intent.getAction();
final int pid = intent.getIntExtra(EXTRA_PID, 0);
- // TODO: temporarily using pid as id until test cases and dumpstate are changed.
- final int id = intent.getIntExtra(EXTRA_ID, pid);
+ final int id = intent.getIntExtra(EXTRA_ID, 0);
final int max = intent.getIntExtra(EXTRA_MAX, -1);
final String name = intent.getStringExtra(EXTRA_NAME);
@@ -474,7 +473,10 @@
+ info + ")");
return;
}
- Log.v(TAG, "Sending 'Progress' notification for id " + info.id + ": " + percentText);
+ if (DEBUG) {
+ Log.d(TAG, "Sending 'Progress' notification for id " + info.id + "(pid " + info.pid
+ + "): " + percentText);
+ }
NotificationManager.from(mContext).notify(TAG, info.id, notification);
}
@@ -541,7 +543,7 @@
final int pid = info.pid;
final int id = info.id;
if (info.finished) {
- if (DEBUG) Log.v(TAG, "Skipping finished process " + pid + "(id: " + id + ")");
+ if (DEBUG) Log.v(TAG, "Skipping finished process " + pid + " (id: " + id + ")");
continue;
}
activeProcesses++;
@@ -708,7 +710,7 @@
}
final File screenshotFile = new File((String) resultMsg.obj);
- final int msgId;
+ final String msg;
if (taken) {
info.addScreenshot(screenshotFile);
if (info.finished) {
@@ -716,14 +718,13 @@
info.renameScreenshots(mScreenshotsDir);
sendBugreportNotification(mContext, info);
}
- msgId = R.string.bugreport_screenshot_taken;
+ msg = mContext.getString(R.string.bugreport_screenshot_taken);
} else {
// TODO: try again using Framework APIs instead of relying on screencap.
- msgId = R.string.bugreport_screenshot_failed;
+ msg = mContext.getString(R.string.bugreport_screenshot_failed);
+ Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
}
- final String msg = mContext.getString(msgId);
Log.d(TAG, msg);
- Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
}
/**
@@ -1125,7 +1126,7 @@
private static boolean setSystemProperty(String key, String value) {
try {
- if (DEBUG) Log.v(TAG, "Setting system property" + key + " to " + value);
+ if (DEBUG) Log.v(TAG, "Setting system property " + key + " to " + value);
SystemProperties.set(key, value);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not set property " + key + " to " + value, e);
@@ -1249,6 +1250,8 @@
* Sets its internal state and displays the dialog.
*/
private void initialize(final Context context, BugreportInfo info) {
+ final String dialogTitle =
+ context.getString(R.string.bugreport_info_dialog_title, info.id);
// First initializes singleton.
if (mDialog == null) {
@SuppressLint("InflateParams")
@@ -1272,7 +1275,7 @@
mDialog = new AlertDialog.Builder(context)
.setView(view)
- .setTitle(context.getString(R.string.bugreport_info_dialog_title, info.id))
+ .setTitle(dialogTitle)
.setCancelable(false)
.setPositiveButton(context.getString(com.android.internal.R.string.ok),
null)
@@ -1297,6 +1300,12 @@
new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG));
+ } else {
+ // Re-use view, but reset fields first.
+ mDialog.setTitle(dialogTitle);
+ mInfoName.setText(null);
+ mInfoTitle.setText(null);
+ mInfoDescription.setText(null);
}
// Then set fields.
@@ -1372,7 +1381,7 @@
// Must update system property for the cases where dumpstate finishes
// while the user is still entering other fields (like title or
// description)
- setBugreportNameProperty(mId, name);
+ setBugreportNameProperty(mPid, name);
}
/**
@@ -1517,7 +1526,7 @@
final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size());
for (File oldFile : screenshotFiles) {
final String oldName = oldFile.getName();
- final String newName = oldName.replace(Integer.toString(pid), name);
+ final String newName = oldName.replaceFirst(Integer.toString(pid), name);
final File newFile;
if (!newName.equals(oldName)) {
final File renamedFile = new File(screenshotDir, newName);
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 8c62670..3cef3bb 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -19,6 +19,7 @@
import static android.test.MoreAsserts.assertContainsRegex;
import static com.android.shell.ActionSendMultipleConsumerActivity.UI_NAME;
import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.EXTRA_ID;
import static com.android.shell.BugreportProgressService.EXTRA_MAX;
import static com.android.shell.BugreportProgressService.EXTRA_NAME;
import static com.android.shell.BugreportProgressService.EXTRA_PID;
@@ -60,6 +61,7 @@
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.text.TextUtils;
@@ -98,24 +100,33 @@
private static final String BUGREPORTS_DIR = "bugreports";
private static final String BUGREPORT_FILE = "test_bugreport.txt";
private static final String ZIP_FILE = "test_bugreport.zip";
+ private static final String ZIP_FILE2 = "test_bugreport2.zip";
private static final String SCREENSHOT_FILE = "test_screenshot.png";
private static final String BUGREPORT_CONTENT = "Dump, might as well dump!\n";
private static final String SCREENSHOT_CONTENT = "A picture is worth a thousand words!\n";
private static final int PID = 42;
- private static final String PROGRESS_PROPERTY = "dumpstate.42.progress";
- private static final String MAX_PROPERTY = "dumpstate.42.max";
- private static final String NAME_PROPERTY = "dumpstate.42.name";
+ private static final int PID2 = 24;
+ private static final int ID = 108;
+ private static final int ID2 = 801;
+ private static final String PROGRESS_PROPERTY = "dumpstate." + PID + ".progress";
+ private static final String MAX_PROPERTY = "dumpstate." + PID + ".max";
+ private static final String NAME_PROPERTY = "dumpstate." + PID + ".name";
private static final String NAME = "BUG, Y U NO REPORT?";
+ private static final String NAME2 = "A bugreport's life";
private static final String NEW_NAME = "Bug_Forrest_Bug";
+ private static final String NEW_NAME2 = "BugsyReportsy";
private static final String TITLE = "Wimbugdom Champion 2015";
+ private static final String TITLE2 = "Master of the Universe";
+ private static final String DESCRIPTION = "One's description...";
+ private static final String DESCRIPTION2 = "...is another's treasure.";
private static final String NO_DESCRIPTION = null;
private static final String NO_NAME = null;
private static final String NO_SCREENSHOT = null;
private static final String NO_TITLE = null;
- private static final int NO_PID = 0;
+ private static final int NO_ID = 0;
private static final boolean RENAMED_SCREENSHOTS = true;
private static final boolean DIDNT_RENAME_SCREENSHOTS = false;
@@ -123,6 +134,7 @@
private String mPlainTextPath;
private String mZipPath;
+ private String mZipPath2;
private String mScreenshotPath;
private Context mContext;
@@ -141,10 +153,12 @@
mPlainTextPath = getPath(BUGREPORT_FILE);
mZipPath = getPath(ZIP_FILE);
+ mZipPath2 = getPath(ZIP_FILE2);
mScreenshotPath = getPath(SCREENSHOT_FILE);
createTextFile(mPlainTextPath, BUGREPORT_CONTENT);
createTextFile(mScreenshotPath, SCREENSHOT_CONTENT);
createZipFile(mZipPath, BUGREPORT_FILE, BUGREPORT_CONTENT);
+ createZipFile(mZipPath2, BUGREPORT_FILE, BUGREPORT_CONTENT);
// Creates a multi-line description.
StringBuilder sb = new StringBuilder();
@@ -177,8 +191,8 @@
assertProgressNotification(NAME, nf.format(0.25));
Bundle extras =
- sendBugreportFinishedAndGetSharedIntent(PID, mPlainTextPath, mScreenshotPath);
- assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, PID, ZIP_FILE,
+ sendBugreportFinishedAndGetSharedIntent(ID, mPlainTextPath, mScreenshotPath);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, ID, PID, ZIP_FILE,
NAME, NO_TITLE, NO_DESCRIPTION, 1, RENAMED_SCREENSHOTS);
assertServiceNotRunning();
@@ -201,15 +215,15 @@
assertScreenshotButtonEnabled(false);
waitForScreenshotButtonEnabled(true);
- sendBugreportFinished(PID, mPlainTextPath, mScreenshotPath);
+ sendBugreportFinished(ID, mPlainTextPath, mScreenshotPath);
if (serviceDies) {
- waitShareNotification(PID);
+ waitShareNotification(ID);
killService();
}
- Bundle extras = acceptBugreportAndGetSharedIntent(PID);
- assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, PID, ZIP_FILE,
+ Bundle extras = acceptBugreportAndGetSharedIntent(ID);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, ID, PID, ZIP_FILE,
NAME, NO_TITLE, NO_DESCRIPTION, 2, RENAMED_SCREENSHOTS);
assertServiceNotRunning();
@@ -227,8 +241,8 @@
resetProperties();
sendBugreportStarted(1000);
- sendBugreportFinished(PID, mPlainTextPath, NO_SCREENSHOT);
- waitShareNotification(PID);
+ sendBugreportFinished(ID, mPlainTextPath, NO_SCREENSHOT);
+ waitShareNotification(ID);
// There's no indication in the UI about the screenshot finish, so just sleep like a baby...
Thread.sleep(SAFE_SCREENSHOT_DELAY * DateUtils.SECOND_IN_MILLIS);
@@ -237,8 +251,8 @@
killService();
}
- Bundle extras = acceptBugreportAndGetSharedIntent(PID);
- assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT, PID, ZIP_FILE,
+ Bundle extras = acceptBugreportAndGetSharedIntent(ID);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT, ID, PID, ZIP_FILE,
NAME, NO_TITLE, NO_DESCRIPTION, 1, RENAMED_SCREENSHOTS);
assertServiceNotRunning();
@@ -249,11 +263,10 @@
sendBugreportStarted(1000);
waitForScreenshotButtonEnabled(true);
- DetailsUi detailsUi = new DetailsUi(mUiBot, PID);
+ DetailsUi detailsUi = new DetailsUi(mUiBot, ID);
// Check initial name.
- String actualName = detailsUi.nameField.getText().toString();
- assertEquals("Wrong value on field 'name'", NAME, actualName);
+ detailsUi.assertName(NAME);
// Change name - it should have changed system property once focus is changed.
detailsUi.nameField.setText(NEW_NAME);
@@ -281,9 +294,9 @@
assertPropertyValue(NAME_PROPERTY, NEW_NAME);
assertProgressNotification(NEW_NAME, "0.00%");
- Bundle extras = sendBugreportFinishedAndGetSharedIntent(PID, mPlainTextPath,
+ Bundle extras = sendBugreportFinishedAndGetSharedIntent(ID, mPlainTextPath,
mScreenshotPath);
- assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, PID, TITLE,
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, ID, PID, TITLE,
NEW_NAME, TITLE, mDescription, 1, RENAMED_SCREENSHOTS);
assertServiceNotRunning();
@@ -302,11 +315,10 @@
sendBugreportStarted(1000);
waitForScreenshotButtonEnabled(true);
- DetailsUi detailsUi = new DetailsUi(mUiBot, PID);
+ DetailsUi detailsUi = new DetailsUi(mUiBot, ID);
// Check initial name.
- String actualName = detailsUi.nameField.getText().toString();
- assertEquals("Wrong value on field 'name'", NAME, actualName);
+ detailsUi.assertName(NAME);
// Change fields.
detailsUi.reOpen();
@@ -319,9 +331,9 @@
assertPropertyValue(NAME_PROPERTY, NEW_NAME);
assertProgressNotification(NEW_NAME, "0.00%");
- Bundle extras = sendBugreportFinishedAndGetSharedIntent(PID,
+ Bundle extras = sendBugreportFinishedAndGetSharedIntent(ID,
plainText? mPlainTextPath : mZipPath, mScreenshotPath);
- assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, PID, TITLE,
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, ID, PID, TITLE,
NEW_NAME, TITLE, mDescription, 1, RENAMED_SCREENSHOTS);
assertServiceNotRunning();
@@ -332,20 +344,66 @@
sendBugreportStarted(1000);
waitForScreenshotButtonEnabled(true);
- DetailsUi detailsUi = new DetailsUi(mUiBot, PID);
+ DetailsUi detailsUi = new DetailsUi(mUiBot, ID);
detailsUi.nameField.setText("");
detailsUi.titleField.setText("");
detailsUi.descField.setText(mDescription);
detailsUi.clickOk();
- Bundle extras = sendBugreportFinishedAndGetSharedIntent(PID, mZipPath, mScreenshotPath);
- assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, PID, ZIP_FILE,
+ Bundle extras = sendBugreportFinishedAndGetSharedIntent(ID, mZipPath, mScreenshotPath);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, ID, PID, ZIP_FILE,
NO_NAME, NO_TITLE, mDescription, 1, DIDNT_RENAME_SCREENSHOTS);
assertServiceNotRunning();
}
+ /*
+ * TODO: this test can be flanky because it relies in the order the notifications are displayed,
+ * since mUiBot gets the first notification.
+ * Ideally, openProgressNotification() should return the whole notification, so DetailsUi
+ * could use it and find children instead, but unfortunately the notification object hierarchy
+ * is too complex and getting it from the notification text object would be to fragile
+ * (for instance, it could require navigating many parents up in the hierarchy).
+ */
+ public void testProgress_changeJustDetailsIsClearedOnSecondBugreport() throws Exception {
+ resetProperties();
+ sendBugreportStarted(ID, PID, NAME, 1000);
+ waitForScreenshotButtonEnabled(true);
+
+ DetailsUi detailsUi = new DetailsUi(mUiBot, ID);
+ detailsUi.assertName(NAME);
+ detailsUi.assertTitle(mContext.getString(R.string.bugreport_info_title));
+ detailsUi.assertDescription(mContext.getString(R.string.bugreport_info_description));
+ detailsUi.nameField.setText(NEW_NAME);
+ detailsUi.titleField.setText(TITLE);
+ detailsUi.descField.setText(DESCRIPTION);
+ detailsUi.clickOk();
+
+ sendBugreportStarted(ID2, PID2, NAME2, 1000);
+
+ Bundle extras = sendBugreportFinishedAndGetSharedIntent(ID, mZipPath, mScreenshotPath);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, ID, PID, TITLE,
+ NEW_NAME, TITLE, DESCRIPTION, 1, RENAMED_SCREENSHOTS);
+
+ detailsUi = new DetailsUi(mUiBot, ID2);
+ detailsUi.assertName(NAME2);
+ detailsUi.assertTitle(mContext.getString(R.string.bugreport_info_title));
+ detailsUi.assertDescription(mContext.getString(R.string.bugreport_info_description));
+ detailsUi.nameField.setText(NEW_NAME2);
+ detailsUi.titleField.setText(TITLE2);
+ detailsUi.descField.setText(DESCRIPTION2);
+ detailsUi.clickOk();
+
+ // Must use a different zip file otherwise it will fail because zip already contains
+ // title.txt and description.txt entries.
+ extras = sendBugreportFinishedAndGetSharedIntent(ID2, mZipPath2, NO_SCREENSHOT);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT, ID2, PID2, TITLE2,
+ NEW_NAME2, TITLE2, DESCRIPTION2, 1, RENAMED_SCREENSHOTS);
+
+ assertServiceNotRunning();
+ }
+
/**
* Tests the scenario where the initial screenshot and dumpstate are finished while the user
* is changing the info in the details screen.
@@ -369,14 +427,14 @@
waitForScreenshotButtonEnabled(true);
}
- DetailsUi detailsUi = new DetailsUi(mUiBot, PID);
+ DetailsUi detailsUi = new DetailsUi(mUiBot, ID);
// Finish the bugreport while user's still typing the name.
detailsUi.nameField.setText(NEW_NAME);
- sendBugreportFinished(PID, mPlainTextPath, mScreenshotPath);
+ sendBugreportFinished(ID, mPlainTextPath, mScreenshotPath);
// Wait until the share notification is received...
- waitShareNotification(PID);
+ waitShareNotification(ID);
// ...then close notification bar.
mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
@@ -390,8 +448,8 @@
detailsUi.clickOk();
// Finally, share bugreport.
- Bundle extras = acceptBugreportAndGetSharedIntent(PID);
- assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, PID, TITLE,
+ Bundle extras = acceptBugreportAndGetSharedIntent(ID);
+ assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT, ID, PID, TITLE,
NAME, TITLE, mDescription, 1, RENAMED_SCREENSHOTS);
assertServiceNotRunning();
@@ -402,8 +460,8 @@
BugreportPrefs.setWarningState(mContext, BugreportPrefs.STATE_SHOW);
// Send notification and click on share.
- sendBugreportFinished(NO_PID, mPlainTextPath, null);
- acceptBugreport(NO_PID);
+ sendBugreportFinished(NO_ID, mPlainTextPath, null);
+ acceptBugreport(NO_ID);
// Handle the warning
mUiBot.getVisibleObject(mContext.getString(R.string.bugreport_confirm));
@@ -425,9 +483,9 @@
}
public void testShareBugreportAfterServiceDies() throws Exception {
- sendBugreportFinished(NO_PID, mPlainTextPath, NO_SCREENSHOT);
+ sendBugreportFinished(NO_ID, mPlainTextPath, NO_SCREENSHOT);
killService();
- Bundle extras = acceptBugreportAndGetSharedIntent(NO_PID);
+ Bundle extras = acceptBugreportAndGetSharedIntent(NO_ID);
assertActionSendMultiple(extras, BUGREPORT_CONTENT, NO_SCREENSHOT);
}
@@ -463,32 +521,38 @@
private void assertProgressNotification(String name, String percent) {
// TODO: it currently looks for 3 distinct objects, without taking advantage of their
// relationship.
- openProgressNotification(PID);
+ openProgressNotification(ID);
Log.v(TAG, "Looking for progress notification details: '" + name + "-" + percent + "'");
mUiBot.getObject(name);
mUiBot.getObject(percent);
}
- private void openProgressNotification(int pid) {
- String title = mContext.getString(R.string.bugreport_in_progress_title, pid);
+ private void openProgressNotification(int id) {
+ String title = mContext.getString(R.string.bugreport_in_progress_title, id);
Log.v(TAG, "Looking for progress notification title: '" + title + "'");
mUiBot.getNotification(title);
}
void resetProperties() {
// TODO: call method to remove property instead
- SystemProperties.set(PROGRESS_PROPERTY, "0");
- SystemProperties.set(MAX_PROPERTY, "0");
+ SystemProperties.set(PROGRESS_PROPERTY, "Reset");
+ SystemProperties.set(MAX_PROPERTY, "Reset");
+ SystemProperties.set(NAME_PROPERTY, "Reset");
}
/**
* Sends a "bugreport started" intent with the default values.
*/
private void sendBugreportStarted(int max) throws Exception {
+ sendBugreportStarted(ID, PID, NAME, max);
+ }
+
+ private void sendBugreportStarted(int id, int pid, String name, int max) throws Exception {
Intent intent = new Intent(INTENT_BUGREPORT_STARTED);
intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- intent.putExtra(EXTRA_PID, PID);
- intent.putExtra(EXTRA_NAME, NAME);
+ intent.putExtra(EXTRA_ID, id);
+ intent.putExtra(EXTRA_PID, pid);
+ intent.putExtra(EXTRA_NAME, name);
intent.putExtra(EXTRA_MAX, max);
mContext.sendBroadcast(intent);
}
@@ -500,7 +564,7 @@
*/
private Bundle sendBugreportFinishedAndGetSharedIntent(String bugreportPath,
String screenshotPath) {
- return sendBugreportFinishedAndGetSharedIntent(NO_PID, bugreportPath, screenshotPath);
+ return sendBugreportFinishedAndGetSharedIntent(NO_ID, bugreportPath, screenshotPath);
}
/**
@@ -508,10 +572,10 @@
*
* @return extras sent in the shared intent.
*/
- private Bundle sendBugreportFinishedAndGetSharedIntent(int pid, String bugreportPath,
+ private Bundle sendBugreportFinishedAndGetSharedIntent(int id, String bugreportPath,
String screenshotPath) {
- sendBugreportFinished(pid, bugreportPath, screenshotPath);
- return acceptBugreportAndGetSharedIntent(pid);
+ sendBugreportFinished(id, bugreportPath, screenshotPath);
+ return acceptBugreportAndGetSharedIntent(id);
}
/**
@@ -519,8 +583,8 @@
*
* @return extras sent in the shared intent.
*/
- private Bundle acceptBugreportAndGetSharedIntent(int pid) {
- acceptBugreport(pid);
+ private Bundle acceptBugreportAndGetSharedIntent(int id) {
+ acceptBugreport(id);
mUiBot.chooseActivity(UI_NAME);
return mListener.getExtras();
}
@@ -528,25 +592,25 @@
/**
* Waits for the notification to share the finished bugreport.
*/
- private void waitShareNotification(int pid) {
- mUiBot.getNotification(mContext.getString(R.string.bugreport_finished_title, pid));
+ private void waitShareNotification(int id) {
+ mUiBot.getNotification(mContext.getString(R.string.bugreport_finished_title, id));
}
/**
* Accepts the notification to share the finished bugreport.
*/
- private void acceptBugreport(int pid) {
- mUiBot.clickOnNotification(mContext.getString(R.string.bugreport_finished_title, pid));
+ private void acceptBugreport(int id) {
+ mUiBot.clickOnNotification(mContext.getString(R.string.bugreport_finished_title, id));
}
/**
* Sends a "bugreport finished" intent.
*/
- private void sendBugreportFinished(int pid, String bugreportPath, String screenshotPath) {
+ private void sendBugreportFinished(int id, String bugreportPath, String screenshotPath) {
Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- if (pid != NO_PID) {
- intent.putExtra(EXTRA_PID, pid);
+ if (id != NO_ID) {
+ intent.putExtra(EXTRA_ID, id);
}
if (bugreportPath != null) {
intent.putExtra(EXTRA_BUGREPORT, bugreportPath);
@@ -563,7 +627,7 @@
*/
private void assertActionSendMultiple(Bundle extras, String bugreportContent,
String screenshotContent) throws IOException {
- assertActionSendMultiple(extras, bugreportContent, screenshotContent, PID, ZIP_FILE,
+ assertActionSendMultiple(extras, bugreportContent, screenshotContent, ID, PID, ZIP_FILE,
NO_NAME, NO_TITLE, NO_DESCRIPTION, 0, DIDNT_RENAME_SCREENSHOTS);
}
@@ -573,6 +637,7 @@
* @param extras extras received in the intent
* @param bugreportContent expected content in the bugreport file
* @param screenshotContent expected content in the screenshot file (sent by dumpstate), if any
+ * @param id emulated dumpstate id
* @param pid emulated dumpstate pid
* @param name expected subject
* @param name bugreport name as provided by the user (or received by dumpstate)
@@ -582,7 +647,7 @@
* @param renamedScreenshots whether the screenshots are expected to be renamed
*/
private void assertActionSendMultiple(Bundle extras, String bugreportContent,
- String screenshotContent, int pid, String subject,
+ String screenshotContent, int id, int pid, String subject,
String name, String title, String description,
int numberScreenshots, boolean renamedScreenshots) throws IOException {
String body = extras.getString(Intent.EXTRA_TEXT);
@@ -745,7 +810,7 @@
fail("Service status didn't change to " + expectRunning);
}
- private static void createTextFile(String path, String content) throws IOException {
+ private void createTextFile(String path, String content) throws IOException {
Log.v(TAG, "createFile(" + path + ")");
try (Writer writer = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(path)))) {
@@ -781,7 +846,7 @@
* Gets the notification button used to take a screenshot.
*/
private UiObject getScreenshotButton() {
- openProgressNotification(PID);
+ openProgressNotification(ID);
return mUiBot.getVisibleObject(
mContext.getString(R.string.bugreport_screenshot_action).toUpperCase());
}
@@ -833,15 +898,16 @@
/**
* Gets the UI objects by opening the progress notification and clicking DETAILS.
*/
- DetailsUi(UiBot uiBot, int pid) throws UiObjectNotFoundException {
- openProgressNotification(pid);
- detailsButton = mUiBot.getVisibleObject(
- mContext.getString(R.string.bugreport_info_action).toUpperCase());
+ DetailsUi(UiBot uiBot, int id) throws UiObjectNotFoundException {
+ openProgressNotification(id);
+ detailsButton = mUiBot.getVisibleObject(mContext.getString(
+ R.string.bugreport_info_action).toUpperCase());
+
mUiBot.click(detailsButton, "details_button");
// TODO: unhardcode resource ids
UiObject dialogTitle = mUiBot.getVisibleObjectById("android:id/alertTitle");
assertEquals("Wrong title", mContext.getString(R.string.bugreport_info_dialog_title,
- pid), dialogTitle.getText().toString());
+ id), dialogTitle.getText().toString());
nameField = mUiBot.getVisibleObjectById("com.android.shell:id/name");
titleField = mUiBot.getVisibleObjectById("com.android.shell:id/title");
descField = mUiBot.getVisibleObjectById("com.android.shell:id/description");
@@ -849,6 +915,28 @@
cancelButton = mUiBot.getObjectById("android:id/button2");
}
+ private void assertField(String name, UiObject field, String expected) {
+ try {
+ String actual = field.getText().toString();
+ assertEquals("Wrong value on field '" + name + "'", expected, actual);
+ } catch (UiObjectNotFoundException e) {
+ // Should not happen...
+ throw new IllegalStateException("field not found: " + name, e);
+ }
+ }
+
+ void assertName(String expected) {
+ assertField("name", nameField, expected);
+ }
+
+ void assertTitle(String expected) {
+ assertField("title", titleField, expected);
+ }
+
+ void assertDescription(String expected) {
+ assertField("description", descField, expected);
+ }
+
/**
* Takes focus away from the name field so it can be validated.
*/
@@ -858,7 +946,7 @@
}
void reOpen() {
- openProgressNotification(PID);
+ openProgressNotification(ID);
mUiBot.click(detailsButton, "details_button");
}
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 88313bb..ad3c26b 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -1,10 +1,24 @@
LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := SystemUI-proto-tags
+
+LOCAL_SRC_FILES := $(call all-proto-files-under,src) \
+ src/com/android/systemui/EventLogTags.logtags
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# ------------------
+
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-proto-files-under,src) $(call all-Iaidl-files-under, src) \
- src/com/android/systemui/EventLogTags.logtags
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := \
Keyguard \
@@ -13,13 +27,12 @@
android-support-v7-appcompat \
android-support-v14-preference \
android-support-v17-leanback \
- framework-protos
+ framework-protos \
+ SystemUI-proto-tags
LOCAL_JAVA_LIBRARIES := telephony-common
LOCAL_PACKAGE_NAME := SystemUI
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
-LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
LOCAL_CERTIFICATE := platform
LOCAL_PRIVILEGED_MODULE := true
diff --git a/packages/SystemUI/res/drawable/tv_pip_button_focused.xml b/packages/SystemUI/res/drawable/tv_pip_button_focused.xml
new file mode 100644
index 0000000..5cabb77a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/tv_pip_button_focused.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <size
+ android:width="36dp"
+ android:height="36dp" />
+ <solid
+ android:color="#4DFFFFFF" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/tv_pip_close_button.xml b/packages/SystemUI/res/drawable/tv_pip_close_button.xml
new file mode 100644
index 0000000..86fda0d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/tv_pip_close_button.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:constantSize="true">
+ <item android:state_focused="true">
+ <layer-list>
+ <item android:drawable="@drawable/tv_pip_button_focused" />
+ <item android:drawable="@drawable/ic_close_white" />
+ </layer-list>
+ </item>
+ <item android:drawable="@drawable/ic_close_white" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/tv_pip_full_button.xml b/packages/SystemUI/res/drawable/tv_pip_full_button.xml
new file mode 100644
index 0000000..332c669
--- /dev/null
+++ b/packages/SystemUI/res/drawable/tv_pip_full_button.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:constantSize="true">
+ <item android:state_focused="true">
+ <layer-list>
+ <item android:drawable="@drawable/tv_pip_button_focused" />
+ <item android:drawable="@drawable/ic_fullscreen_white_24dp" />
+ </layer-list>
+ </item>
+ <item android:drawable="@drawable/ic_fullscreen_white_24dp" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/tv_pip_pause_button.xml b/packages/SystemUI/res/drawable/tv_pip_pause_button.xml
new file mode 100644
index 0000000..d277b07
--- /dev/null
+++ b/packages/SystemUI/res/drawable/tv_pip_pause_button.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:constantSize="true">
+ <item android:state_focused="true">
+ <layer-list>
+ <item android:drawable="@drawable/tv_pip_button_focused" />
+ <item android:drawable="@drawable/ic_pause_white_24dp" />
+ </layer-list>
+ </item>
+ <item android:drawable="@drawable/ic_pause_white_24dp" />
+</selector>
diff --git a/packages/SystemUI/res/layout/docked_stack_divider.xml b/packages/SystemUI/res/layout/docked_stack_divider.xml
index 7ea5027..cfaf018 100644
--- a/packages/SystemUI/res/layout/docked_stack_divider.xml
+++ b/packages/SystemUI/res/layout/docked_stack_divider.xml
@@ -27,6 +27,7 @@
<com.android.systemui.stackdivider.DividerHandleView
style="@style/DockedDividerHandle"
android:id="@+id/docked_divider_handle"
+ android:contentDescription="@string/accessibility_divider"
android:background="@null"/>
</com.android.systemui.stackdivider.DividerView>
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 3ee2475..994d3c9 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -28,9 +28,7 @@
android:layout_marginTop="@dimen/status_bar_header_height"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingBottom="8dp"
- android:clipToPadding="false"
- android:clipChildren="false" />
+ android:paddingBottom="8dp" />
<include layout="@layout/quick_status_bar_expanded_header" />
diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml
index 2e41561..fa65758 100644
--- a/packages/SystemUI/res/layout/recents_task_view_header.xml
+++ b/packages/SystemUI/res/layout/recents_task_view_header.xml
@@ -30,21 +30,41 @@
android:paddingBottom="8dp"
android:paddingStart="12dp"
android:paddingEnd="16dp" />
- <TextView
- android:id="@+id/title"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:layout_marginStart="56dp"
- android:layout_marginEnd="112dp"
- android:textSize="16sp"
- android:textColor="#ffffffff"
- android:text="@string/recents_empty_message"
- android:fontFamily="sans-serif-medium"
- android:singleLine="true"
- android:maxLines="2"
- android:ellipsize="marquee"
- android:fadingEdge="horizontal" />
+ android:layout_marginEnd="56dp"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:textSize="16sp"
+ android:textColor="#ffffffff"
+ android:text="@string/recents_empty_message"
+ android:fontFamily="sans-serif-medium"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+ <TextView
+ android:id="@+id/sub_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:textSize="11sp"
+ android:textColor="#ffffffff"
+ android:text="@string/recents_launch_non_dockable_task_label"
+ android:fontFamily="sans-serif-medium"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:visibility="gone" />
+ </LinearLayout>
<com.android.systemui.recents.views.FixedSizeImageView
android:id="@+id/move_task"
android:layout_width="@dimen/recents_task_view_header_button_width"
diff --git a/packages/SystemUI/res/layout/tv_pip_menu.xml b/packages/SystemUI/res/layout/tv_pip_menu.xml
index 3562c644..0b98d0e 100644
--- a/packages/SystemUI/res/layout/tv_pip_menu.xml
+++ b/packages/SystemUI/res/layout/tv_pip_menu.xml
@@ -18,36 +18,94 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_gravity="end"
- android:paddingStart="10dp"
- android:paddingEnd="10dp"
- android:background="#88FFFFFF"
- android:gravity="center_vertical" >
+ android:orientation="horizontal"
+ android:paddingTop="350dp"
+ android:background="#CC000000"
+ android:gravity="top|center_horizontal"
+ android:clipChildren="false">
- <Button android:id="@+id/full"
- android:layout_width="match_parent"
+ <LinearLayout
+ android:layout_width="34dp"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:text="@string/pip_fullscreen"
- android:textSize="10sp"
- android:focusable="true" />
+ android:layout_marginEnd="3dp"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:clipChildren="false">
- <Button android:id="@+id/exit"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:text="@string/pip_exit"
- android:textSize="10sp"
- android:focusable="true" />
+ <ImageView android:id="@+id/full"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:src="@drawable/tv_pip_full_button" />
- <Button android:id="@+id/cancel"
- android:layout_width="match_parent"
+ <TextView android:id="@+id/full_desc"
+ android:layout_width="100dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="3dp"
+ android:gravity="center"
+ android:visibility="invisible"
+ android:text="@string/pip_fullscreen"
+ android:fontFamily="sans-serif"
+ android:textSize="12sp"
+ android:textColor="#EEEEEE"
+ android:clipChildren="false" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="34dp"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:text="@string/pip_cancel"
- android:textSize="10sp"
- android:focusable="true" />
+ android:layout_marginStart="3dp"
+ android:layout_marginEnd="3dp"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:visibility="gone"
+ android:clipChildren="false">
+
+ <ImageView android:id="@+id/play_pause"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:src="@drawable/tv_pip_pause_button" />
+
+ <TextView android:id="@+id/play_pause_desc"
+ android:layout_width="100dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="3dp"
+ android:gravity="center"
+ android:visibility="invisible"
+ android:text="@string/pip_pause"
+ android:fontFamily="sans-serif"
+ android:textSize="12sp"
+ android:textColor="#EEEEEE"
+ android:clipChildren="false" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="34dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="3dp"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:clipChildren="false">
+
+ <ImageView android:id="@+id/close"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="true"
+ android:src="@drawable/tv_pip_close_button" />
+
+ <TextView android:id="@+id/close_desc"
+ android:layout_width="100dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="3dp"
+ android:gravity="center"
+ android:visibility="invisible"
+ android:text="@string/pip_close"
+ android:fontFamily="sans-serif"
+ android:textSize="12sp"
+ android:textColor="#EEEEEE"
+ android:clipChildren="false" />
+ </LinearLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/values-sw600dp/styles.xml b/packages/SystemUI/res/values-sw600dp/styles.xml
index 4d7d6b5..791d761 100644
--- a/packages/SystemUI/res/values-sw600dp/styles.xml
+++ b/packages/SystemUI/res/values-sw600dp/styles.xml
@@ -16,7 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="BrightnessDialogContainer" parent="@style/BaseBrightnessDialogContainer">
- <item name="android:layout_width">480dp</item>
+ <item name="android:layout_width">@dimen/standard_notification_panel_width</item>
</style>
<style name="UserDetailView">
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 87aedab..b9eee2e 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -44,6 +44,10 @@
<item type="id" name="notification_screenshot"/>
<item type="id" name="notification_hidden"/>
<item type="id" name="notification_volumeui"/>
+ <item type="id" name="transformation_start_x_tag"/>
+ <item type="id" name="transformation_start_y_tag"/>
+ <item type="id" name="transformation_start_scale_x_tag"/>
+ <item type="id" name="transformation_start_scale_y_tag"/>
<!-- Whether the icon is from a notification for which targetSdk < L -->
<item type="id" name="icon_is_pre_L"/>
@@ -56,5 +60,11 @@
<item type="id" name="image_icon_tag" />
<item type="id" name="contains_transformed_view" />
<item type="id" name="is_clicked_heads_up_tag" />
+
+ <!-- Accessibility actions for the docked stack divider -->
+ <item type="id" name="action_move_left" />
+ <item type="id" name="action_move_right" />
+ <item type="id" name="action_move_up" />
+ <item type="id" name="action_move_down" />
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6f6e515..ac6e3ac 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -722,6 +722,10 @@
<string name="recents_history_button_label">History</string>
<!-- Recents: History clear all string. [CHAR LIMIT=NONE] -->
<string name="recents_history_clear_all_button_label">Clear</string>
+ <!-- Recents: Non-dockable task drag message. [CHAR LIMIT=NONE] -->
+ <string name="recents_drag_non_dockable_task_message">This app does not support multi-window</string>
+ <!-- Recents: Non-dockable task launch sub header. [CHAR LIMIT=NONE] -->
+ <string name="recents_launch_non_dockable_task_label">App does not support multi-window</string>
<!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] -->
<string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string>
@@ -1430,4 +1434,18 @@
<item>Don\'t show this icon</item>
</string-array>
+ <!-- Accessibility label for the divider that separates the windows in split-screen mode [CHAR LIMIT=NONE] -->
+ <string name="accessibility_divider">Split-screen divider</string>
+
+ <!-- Accessibility action for moving down the docked stack divider [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_divider_move_down">Move down</string>
+
+ <!-- Accessibility action for moving down the docked stack divider [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_divider_move_up">Move up</string>
+
+ <!-- Accessibility action for moving down the docked stack divider [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_divider_move_left">Move left</string>
+
+ <!-- Accessibility action for moving down the docked stack divider [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_divider_move_right">Move right</string>
</resources>
diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml
index 7c4768d..9ef0343 100644
--- a/packages/SystemUI/res/values/strings_tv.xml
+++ b/packages/SystemUI/res/values/strings_tv.xml
@@ -20,7 +20,7 @@
<!-- Picture-in-Picture menu -->
<eat-comment />
<!-- Button to close PIP on PIP UI -->
- <string name="pip_exit" translatable="false">Close PIP</string>
+ <string name="pip_close" translatable="false">Close PIP</string>
<!-- Button to move PIP screen to the fullscreen on PIP UI -->
<string name="pip_fullscreen" translatable="false">Full screen</string>
<!-- Button to play the current media on PIP UI -->
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
index 735a7c4..c09376b 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
@@ -23,6 +23,7 @@
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
+import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.view.MotionEvent;
@@ -64,6 +65,7 @@
private boolean mBouncerOn = false;
private boolean mSessionActive = false;
private int mState = StatusBarState.SHADE;
+ private boolean mScreenOn;
protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@Override
@@ -77,6 +79,7 @@
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
mDataCollector = DataCollector.getInstance(mContext);
mHumanInteractionClassifier = HumanInteractionClassifier.getInstance(mContext);
+ mScreenOn = context.getSystemService(PowerManager.class).isInteractive();
mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(ENFORCE_BOUNCER), false,
@@ -98,17 +101,21 @@
ENFORCE_BOUNCER, 0);
}
+ private boolean shouldSessionBeActive() {
+ return isEnabled() && mScreenOn &&
+ (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED);
+ }
+
private boolean sessionEntrypoint() {
- if (!mSessionActive && isEnabled() &&
- (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)) {
+ if (!mSessionActive && shouldSessionBeActive()) {
onSessionStart();
return true;
}
return false;
}
- private void sessionExitpoint() {
- if (mSessionActive) {
+ private void sessionExitpoint(boolean force) {
+ if (mSessionActive && (force || !shouldSessionBeActive())) {
mSessionActive = false;
mSensorManager.unregisterListener(this);
}
@@ -167,15 +174,22 @@
public void setStatusBarState(int state) {
mState = state;
+ if (shouldSessionBeActive()) {
+ sessionEntrypoint();
+ } else {
+ sessionExitpoint(false /* force */);
+ }
}
public void onScreenTurningOn() {
+ mScreenOn = true;
if (sessionEntrypoint()) {
mDataCollector.onScreenTurningOn();
}
}
public void onScreenOnFromTouch() {
+ mScreenOn = true;
if (sessionEntrypoint()) {
mDataCollector.onScreenOnFromTouch();
}
@@ -183,12 +197,13 @@
public void onScreenOff() {
mDataCollector.onScreenOff();
- sessionExitpoint();
+ mScreenOn = false;
+ sessionExitpoint(false /* force */);
}
public void onSucccessfulUnlock() {
mDataCollector.onSucccessfulUnlock();
- sessionExitpoint();
+ sessionExitpoint(true /* force */);
}
public void onBouncerShown() {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
index 7ddbdf0..45eb9ad 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/HumanInteractionClassifier.java
@@ -119,7 +119,7 @@
return;
}
- // If the user is dragging down the notification, he might want to drag it down
+ // If the user is dragging down the notification, they might want to drag it down
// enough to see the content, read it for a while and then lift the finger to open
// the notification. This kind of motion scores very bad in the Classifier so the
// MotionEvents which are close to the current position of the finger are not
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 0915ee1..2c5cb89 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -38,7 +38,7 @@
if (mPageIndicator == null) return;
mPageIndicator.setLocation(position);
if (mPageListener != null) {
- mPageListener.onPageChanged(position);
+ mPageListener.onPageChanged(position == 0);
}
}
@@ -47,6 +47,9 @@
int positionOffsetPixels) {
if (mPageIndicator == null) return;
mPageIndicator.setLocation(position + positionOffset);
+ if (mPageListener != null) {
+ mPageListener.onPageChanged(position == 0 && positionOffsetPixels == 0);
+ }
}
@Override
@@ -57,6 +60,11 @@
}
@Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ @Override
protected void onFinishInflate() {
super.onFinishInflate();
mPageIndicator = (PageIndicator) findViewById(R.id.page_indicator);
@@ -209,6 +217,6 @@
};
public interface PageListener {
- void onPageChanged(int page);
+ void onPageChanged(boolean isFirst);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index dc64591..c31bb33 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -69,11 +69,13 @@
}
@Override
- public void onPageChanged(int page) {
- mOnFirstPage = page == 0;
- if (!mOnFirstPage) {
+ public void onPageChanged(boolean isFirst) {
+ if (mOnFirstPage == isFirst) return;
+ if (!isFirst) {
+ setPosition(1);
clearAnimationState();
}
+ mOnFirstPage = isFirst;
}
private void updateAnimators() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index abe4c77..4408dbf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -183,5 +183,10 @@
// No resources here.
return false;
}
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
index 94d8524..026dd0e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TouchAnimator.java
@@ -58,7 +58,7 @@
}
if (mListener != null) {
if (mLastT == 0 || mLastT == 1) {
- if (t != 0) {
+ if (t != mLastT) {
mListener.onAnimationStarted();
}
} else if (t == 1) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index c2a6108..c41098f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -528,17 +528,16 @@
@Override
public void onMultiWindowChanged(boolean inMultiWindow) {
super.onMultiWindowChanged(inMultiWindow);
- if (!inMultiWindow) {
- RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
- launchOpts.loadIcons = false;
- launchOpts.loadThumbnails = false;
- launchOpts.onlyLoadForCache = true;
- RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
- loader.preloadTasks(loadPlan, -1, false);
- loader.loadTasks(this, loadPlan, launchOpts);
- EventBus.getDefault().send(new TaskStackUpdatedEvent(loadPlan.getTaskStack()));
- }
+ RecentsTaskLoader loader = Recents.getTaskLoader();
+ RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+ launchOpts.loadIcons = false;
+ launchOpts.loadThumbnails = false;
+ launchOpts.onlyLoadForCache = true;
+ RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
+ loader.preloadTasks(loadPlan, -1, false);
+ loader.loadTasks(this, loadPlan, launchOpts);
+ EventBus.getDefault().send(new TaskStackUpdatedEvent(loadPlan.getTaskStack(),
+ inMultiWindow));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 8de964b..28b2fae 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -161,10 +161,8 @@
Handler mHandler;
TaskStackListenerImpl mTaskStackListener;
RecentsAppWidgetHost mAppWidgetHost;
- boolean mBootCompleted;
boolean mCanReuseTaskStackViews = true;
boolean mDraggingInRecents;
- boolean mReloadTasks;
boolean mLaunchedWhileDocking;
// Task launching
@@ -236,7 +234,6 @@
}
public void onBootCompleted() {
- mBootCompleted = true;
updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
}
@@ -317,23 +314,21 @@
}
public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
- if (mBootCompleted) {
- if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
- // The user has released alt-tab before the trigger has run, so just show the next
- // task immediately
- showNextTask();
+ if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
+ // The user has released alt-tab before the trigger has run, so just show the next
+ // task immediately
+ showNextTask();
- // Cancel the fast alt-tab trigger
- mFastAltTabTrigger.stopDozing();
- mFastAltTabTrigger.resetTrigger();
- return;
- }
-
- // Defer to the activity to handle hiding recents, if it handles it, then it must still
- // be visible
- EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
- triggeredFromHomeKey));
+ // Cancel the fast alt-tab trigger
+ mFastAltTabTrigger.stopDozing();
+ mFastAltTabTrigger.resetTrigger();
+ return;
}
+
+ // Defer to the activity to handle hiding recents, if it handles it, then it must still
+ // be visible
+ EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
+ triggeredFromHomeKey));
}
public void toggleRecents() {
@@ -347,7 +342,6 @@
mTriggeredFromAltTab = false;
try {
- ViewConfiguration viewConfig = ViewConfiguration.get(mContext);
SystemServicesProxy ssp = Recents.getSystemServices();
ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
MutableBoolean isTopTaskHome = new MutableBoolean(true);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
index f87f6de..0d614e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
@@ -28,8 +28,10 @@
* A new TaskStack instance representing the latest stack state.
*/
public final TaskStack stack;
+ public final boolean inMultiWindow;
- public TaskStackUpdatedEvent(TaskStack stack) {
+ public TaskStackUpdatedEvent(TaskStack stack, boolean inMultiWindow) {
this.stack = stack;
+ this.inMultiWindow = inMultiWindow;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 824231a..6fef8a2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -198,8 +198,8 @@
// Add the task to the stack
Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
thumbnail, title, contentDescription, dismissDescription, activityColor,
- backgroundColor, !isStackTask, isLaunchTarget, isSystemApp, t.bounds,
- t.taskDescription);
+ backgroundColor, !isStackTask, isLaunchTarget, isSystemApp, t.isDockable,
+ t.bounds, t.taskDescription);
allTasks.add(task);
affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 8ed6dd7..e5d4f1b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -168,6 +168,8 @@
public boolean isHistorical;
@ViewDebug.ExportedProperty(category="recents")
public boolean isSystemApp;
+ @ViewDebug.ExportedProperty(category="recents")
+ public boolean isDockable;
private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>();
@@ -178,8 +180,8 @@
public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
Bitmap thumbnail, String title, String contentDescription,
String dismissDescription, int colorPrimary, int colorBackground,
- boolean isHistorical, boolean isLaunchTarget, boolean isSystemApp, Rect bounds,
- ActivityManager.TaskDescription taskDescription) {
+ boolean isHistorical, boolean isLaunchTarget, boolean isSystemApp,
+ boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription) {
boolean isInAffiliationGroup = (affiliationTaskId != key.id);
boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
this.key = key;
@@ -199,6 +201,7 @@
this.isLaunchTarget = isLaunchTarget;
this.isHistorical = isHistorical;
this.isSystemApp = isSystemApp;
+ this.isDockable = isDockable;
}
/** Copies the other task. */
@@ -219,6 +222,7 @@
this.isLaunchTarget = o.isLaunchTarget;
this.isHistorical = o.isHistorical;
this.isSystemApp = o.isSystemApp;
+ this.isDockable = o.isDockable;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 2e45627..5dde926 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -663,8 +663,10 @@
}
public final void onBusEvent(TaskStackUpdatedEvent event) {
- mStack.setTasks(event.stack.computeAllTasksList(), true /* notifyStackChanges */);
- mStack.createAffiliatedGroupings(getContext());
+ if (!event.inMultiWindow) {
+ mStack.setTasks(event.stack.computeAllTasksList(), true /* notifyStackChanges */);
+ mStack.createAffiliatedGroupings(getContext());
+ }
}
public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index 016d937..079d7b9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -21,7 +21,9 @@
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
+import android.widget.Toast;
+import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
@@ -148,11 +150,16 @@
mVisibleDockStates.clear();
if (!ssp.hasDockedTask() && mRv.getTaskStack().getTaskCount() > 1) {
- // Add the dock state drop targets (these take priority)
- TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation();
- for (TaskStack.DockState dockState : dockStates) {
- registerDropTargetForCurrentDrag(dockState);
- mVisibleDockStates.add(dockState);
+ if (!event.task.isDockable) {
+ Toast.makeText(mRv.getContext(), R.string.recents_drag_non_dockable_task_message,
+ Toast.LENGTH_SHORT).show();
+ } else {
+ // Add the dock state drop targets (these take priority)
+ TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation();
+ for (TaskStack.DockState dockState : dockStates) {
+ registerDropTargetForCurrentDrag(dockState);
+ mVisibleDockStates.add(dockState);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index d71d70f..e1a81c8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -1573,6 +1573,9 @@
Task launchTask = mStack.getStackTasks().get(launchTaskIndex);
EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(launchTask),
launchTask, null, INVALID_STACK_ID, false /* screenPinningRequested */));
+
+ MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK,
+ launchTask.key.getComponent().toString());
}
}
@@ -1830,12 +1833,28 @@
}
public final void onBusEvent(TaskStackUpdatedEvent event) {
- // Scroll the stack to the front after it has been updated
+ if (!event.inMultiWindow) {
+ // Scroll the stack to the front after it has been updated
+ event.addPostAnimationCallback(new Runnable() {
+ @Override
+ public void run() {
+ mStackScroller.animateScroll(mLayoutAlgorithm.mMaxScrollP,
+ null /* postScrollRunnable */);
+ }
+ });
+ }
+ // When the multi-window state changes, rebind all task view headers again to update their
+ // dockable state
event.addPostAnimationCallback(new Runnable() {
@Override
public void run() {
- mStackScroller.animateScroll(mLayoutAlgorithm.mMaxScrollP,
- null /* postScrollRunnable */);
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
+ tv.getHeaderView().rebindToTask(tv.getTask(), tv.mTouchExplorationEnabled,
+ tv.mIsDisabledInSafeMode);
+ }
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 0543dd5..05a8527 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -139,6 +139,7 @@
// Header views
ImageView mIconView;
TextView mTitleView;
+ TextView mSubTitleView;
ImageView mMoveTaskButton;
ImageView mDismissButton;
ViewStub mAppOverlayViewStub;
@@ -237,6 +238,7 @@
mIconView.setClickable(false);
mIconView.setOnLongClickListener(this);
mTitleView = (TextView) findViewById(R.id.title);
+ mSubTitleView = (TextView) findViewById(R.id.sub_title);
mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
if (ssp.hasFreeformWorkspaceSupport()) {
mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
@@ -367,6 +369,7 @@
/** Binds the bar view to the task */
public void rebindToTask(Task t, boolean touchExplorationEnabled, boolean disabledInSafeMode) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
mTask = t;
// If an activity icon is defined, then we use that as the primary icon to show in the bar,
@@ -384,6 +387,13 @@
mTitleView.setContentDescription(t.contentDescription);
mTitleView.setTextColor(t.useLightOnPrimaryColor ?
mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
+ if (!t.isDockable && ssp.hasDockedTask()) {
+ mSubTitleView.setVisibility(View.VISIBLE);
+ mSubTitleView.setTextColor(t.useLightOnPrimaryColor ?
+ mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
+ } else {
+ mSubTitleView.setVisibility(View.GONE);
+ }
mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
mLightDismissDrawable : mDarkDismissDrawable);
mDismissButton.setContentDescription(t.dismissDescription);
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerHandleView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerHandleView.java
index 9118e9c..d5f7b39 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerHandleView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerHandleView.java
@@ -26,7 +26,7 @@
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Property;
-import android.widget.ImageButton;
+import android.view.View;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -34,7 +34,7 @@
/**
* View for the handle in the docked stack divider.
*/
-public class DividerHandleView extends ImageButton {
+public class DividerHandleView extends View {
private final static Property<DividerHandleView, Integer> WIDTH_PROPERTY
= new Property<DividerHandleView, Integer>(Integer.class, "width") {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 65a2f8f..da5cbe7 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -27,6 +27,7 @@
import android.graphics.Rect;
import android.graphics.Region.Op;
import android.hardware.display.DisplayManager;
+import android.os.Bundle;
import android.util.AttributeSet;
import android.view.Display;
import android.view.DisplayInfo;
@@ -40,6 +41,8 @@
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
@@ -132,6 +135,42 @@
private boolean mGrowRecents;
private Animator mCurrentAnimator;
+ private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ if (isHorizontalDivision()) {
+ info.addAction(new AccessibilityAction(R.id.action_move_up,
+ mContext.getString(R.string.accessibility_action_divider_move_up)));
+ info.addAction(new AccessibilityAction(R.id.action_move_down,
+ mContext.getString(R.string.accessibility_action_divider_move_down)));
+ } else {
+ info.addAction(new AccessibilityAction(R.id.action_move_left,
+ mContext.getString(R.string.accessibility_action_divider_move_left)));
+ info.addAction(new AccessibilityAction(R.id.action_move_right,
+ mContext.getString(R.string.accessibility_action_divider_move_right)));
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (action == R.id.action_move_up || action == R.id.action_move_down
+ || action == R.id.action_move_left || action == R.id.action_move_right) {
+ int position = getCurrentPosition();
+ SnapTarget currentTarget = mSnapAlgorithm.calculateSnapTarget(
+ position, 0 /* velocity */);
+ SnapTarget nextTarget =
+ action == R.id.action_move_up || action == R.id.action_move_left
+ ? mSnapAlgorithm.getPreviousTarget(currentTarget)
+ : mSnapAlgorithm.getNextTarget(currentTarget);
+ startDragging(true /* animate */, false /* touching */);
+ stopDragging(getCurrentPosition(), nextTarget, 250, Interpolators.FAST_OUT_SLOW_IN);
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+ };
+
public DividerView(Context context) {
super(context);
}
@@ -171,6 +210,7 @@
mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
landscape ? STYLE_HORIZONTAL_DOUBLE_ARROW : STYLE_VERTICAL_DOUBLE_ARROW));
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ mHandle.setAccessibilityDelegate(mHandleDelegate);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index a0c63be..afee846 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1872,7 +1872,7 @@
return entry;
}
- protected StatusBarIconView createIcon(StatusBarNotification sbn) {
+ public StatusBarIconView createIcon(StatusBarNotification sbn) {
// Construct the icon.
Notification n = sbn.getNotification();
final StatusBarIconView iconView = new StatusBarIconView(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 212d290..123dc69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -19,6 +19,7 @@
import android.view.View;
import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
* A helper to fade views in and out.
@@ -44,7 +45,34 @@
if (view.hasOverlappingRendering()) {
view.animate().withLayer();
}
+ }
+ public static void fadeOut(View view, float fadeOutAmount) {
+ view.animate().cancel();
+ if (fadeOutAmount == 1.0f) {
+ view.setVisibility(View.INVISIBLE);
+ } else if (view.getVisibility() == View.INVISIBLE) {
+ view.setVisibility(View.VISIBLE);
+ }
+ fadeOutAmount = mapToFadeDuration(fadeOutAmount);
+ float alpha = Interpolators.ALPHA_OUT.getInterpolation(1.0f - fadeOutAmount);
+ view.setAlpha(alpha);
+ updateLayerType(view, alpha);
+ }
+
+ private static float mapToFadeDuration(float fadeOutAmount) {
+ // Assuming a linear interpolator, we can easily map it to our new duration
+ float endPoint = (float) ANIMATION_DURATION_LENGTH
+ / (float) StackStateAnimator.ANIMATION_DURATION_STANDARD;
+ return Math.min(fadeOutAmount / endPoint, 1.0f);
+ }
+
+ private static void updateLayerType(View view, float alpha) {
+ if (view.hasOverlappingRendering() && alpha > 0.0f && alpha < 1.0f) {
+ view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) {
+ view.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
}
public static void fadeIn(final View view) {
@@ -62,4 +90,15 @@
view.animate().withLayer();
}
}
+
+ public static void fadeIn(View view, float fadeInAmount) {
+ view.animate().cancel();
+ if (view.getVisibility() == View.INVISIBLE) {
+ view.setVisibility(View.VISIBLE);
+ }
+ fadeInAmount = mapToFadeDuration(fadeInAmount);
+ float alpha = Interpolators.ALPHA_IN.getInterpolation(fadeInAmount);
+ view.setAlpha(alpha);
+ updateLayerType(view, alpha);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index c72cec3..da125d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -868,6 +868,10 @@
public void setUserLocked(boolean userLocked) {
mUserLocked = userLocked;
+ mPrivateLayout.setUserExpanding(userLocked);
+ if (mIsSummaryWithChildren) {
+ mChildrenContainer.setUserLocked(userLocked);
+ }
}
/**
@@ -1174,6 +1178,9 @@
int contentHeight = Math.max(getMinHeight(), height);
mPrivateLayout.setContentHeight(contentHeight);
mPublicLayout.setContentHeight(contentHeight);
+ if (mIsSummaryWithChildren) {
+ mChildrenContainer.setActualHeight(height);
+ }
if (mGuts != null) {
mGuts.setActualHeight(height);
}
@@ -1259,7 +1266,7 @@
return mMaxExpandHeight != 0;
}
- private NotificationContentView getShowingLayout() {
+ public NotificationContentView getShowingLayout() {
return mShowingPublic ? mPublicLayout : mPrivateLayout;
}
@@ -1295,8 +1302,15 @@
}
@Override
- public boolean needsIncreasedPadding() {
- return mIsSummaryWithChildren && isGroupExpanded();
+ public float getIncreasedPaddingAmount() {
+ if (mIsSummaryWithChildren) {
+ if (isGroupExpanded()) {
+ return 1.0f;
+ } else if (isUserLocked()) {
+ return mChildrenContainer.getChildExpandFraction();
+ }
+ }
+ return 0.0f;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index c9e1cff..1ff87f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -391,8 +391,11 @@
public void setShadowAlpha(float shadowAlpha) {
}
- public boolean needsIncreasedPadding() {
- return false;
+ /**
+ * @return an amount between 0 and 1 of increased padding that this child needs
+ */
+ public float getIncreasedPaddingAmount() {
+ return 0.0f;
}
public boolean mustStayOnScreen() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index bc85922..977b37e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -48,6 +48,7 @@
private static final int VISIBLE_TYPE_EXPANDED = 1;
private static final int VISIBLE_TYPE_HEADSUP = 2;
private static final int VISIBLE_TYPE_SINGLELINE = 3;
+ private static final int UNDEFINED = -1;
private final Rect mClipBounds = new Rect();
private final int mMinContractedHeight;
@@ -102,6 +103,8 @@
private boolean mExpandable;
private boolean mClipToActualHeight = true;
private ExpandableNotificationRow mContainingNotification;
+ private int mTransformationStartVisibleType;
+ private boolean mUserExpanding;
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -349,6 +352,41 @@
invalidateOutline();
}
+ private void updateContentTransformation() {
+ int visibleType = calculateVisibleType();
+ if (visibleType != mVisibleType) {
+ // A new transformation starts
+ mTransformationStartVisibleType = mVisibleType;
+ final TransformableView shownView = getTransformableViewForVisibleType(visibleType);
+ final TransformableView hiddenView = getTransformableViewForVisibleType(
+ mTransformationStartVisibleType);
+ shownView.transformFrom(hiddenView, 0.0f);
+ getViewForVisibleType(visibleType).setVisibility(View.VISIBLE);
+ hiddenView.transformTo(shownView, 0.0f);
+ mVisibleType = visibleType;
+ }
+ if (mTransformationStartVisibleType != UNDEFINED
+ && mVisibleType != mTransformationStartVisibleType) {
+ final TransformableView shownView = getTransformableViewForVisibleType(mVisibleType);
+ final TransformableView hiddenView = getTransformableViewForVisibleType(
+ mTransformationStartVisibleType);
+ float transformationAmount = calculateTransformationAmount();
+ shownView.transformFrom(hiddenView, transformationAmount);
+ hiddenView.transformTo(shownView, transformationAmount);
+ } else {
+ updateViewVisibilities(visibleType);
+ }
+ }
+
+ private float calculateTransformationAmount() {
+ int startHeight = getViewForVisibleType(mTransformationStartVisibleType).getHeight();
+ int endHeight = getViewForVisibleType(mVisibleType).getHeight();
+ int progress = Math.abs(mContentHeight - startHeight);
+ int totalDistance = Math.abs(endHeight - startHeight);
+ float amount = (float) progress / (float) totalDistance;
+ return Math.min(1.0f, amount);
+ }
+
public int getContentHeight() {
return mContentHeight;
}
@@ -363,10 +401,14 @@
}
public int getMinHeight() {
- if (mIsChildInGroup && !isGroupExpanded()) {
- return mSingleLineView.getHeight();
- } else {
+ return getMinHeight(false /* likeGroupExpanded */);
+ }
+
+ public int getMinHeight(boolean likeGroupExpanded) {
+ if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) {
return mContractedChild.getHeight();
+ } else {
+ return mSingleLineView.getHeight();
}
}
@@ -397,6 +439,10 @@
if (mContractedChild == null) {
return;
}
+ if (mUserExpanding) {
+ updateContentTransformation();
+ return;
+ }
int visibleType = calculateVisibleType();
if (visibleType != mVisibleType || force) {
if (animate && ((visibleType == VISIBLE_TYPE_EXPANDED && mExpandedChild != null)
@@ -492,13 +538,28 @@
* @return one of the static enum types in this view, calculated form the current state
*/
private int calculateVisibleType() {
- boolean noExpandedChild = mExpandedChild == null;
-
+ if (mUserExpanding) {
+ int height = !mIsChildInGroup || isGroupExpanded()
+ || mContainingNotification.isExpanded()
+ ? mContainingNotification.getMaxContentHeight()
+ : mContainingNotification.getShowingLayout().getMinHeight();
+ int expandedVisualType = getVisualTypeForHeight(height);
+ int collapsedVisualType = getVisualTypeForHeight(
+ mContainingNotification.getMinExpandHeight());
+ return mTransformationStartVisibleType == collapsedVisualType
+ ? expandedVisualType
+ : collapsedVisualType;
+ }
int viewHeight = Math.min(mContentHeight, mContainingNotification.getIntrinsicHeight());
+ return getVisualTypeForHeight(viewHeight);
+ }
+
+ private int getVisualTypeForHeight(float viewHeight) {
+ boolean noExpandedChild = mExpandedChild == null;
if (!noExpandedChild && viewHeight == mExpandedChild.getHeight()) {
return VISIBLE_TYPE_EXPANDED;
}
- if (mIsChildInGroup && !isGroupExpanded()) {
+ if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded()) {
return VISIBLE_TYPE_SINGLELINE;
}
@@ -723,4 +784,15 @@
updateSingleLineView();
}
}
+
+ public void setUserExpanding(boolean userExpanding) {
+ mUserExpanding = userExpanding;
+ if (userExpanding) {
+ mTransformationStartVisibleType = mVisibleType;
+ } else {
+ mTransformationStartVisibleType = UNDEFINED;
+ mVisibleType = calculateVisibleType();
+ updateViewVisibilities(mVisibleType);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
index 38b6497..009eed7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/TransformableView.java
@@ -30,6 +30,7 @@
/**
* Get the current state of a view in a transform animation
+ *
* @param fadingView which view we are interested in
* @return the current transform state of this viewtype
*/
@@ -37,18 +38,37 @@
/**
* Transform to the given view
+ *
* @param notification the view to transform to
*/
void transformTo(TransformableView notification, Runnable endRunnable);
/**
+ * Transform to the given view by a specified amount.
+ *
+ * @param notification the view to transform to
+ * @param transformationAmount how much transformation should be done
+ */
+ void transformTo(TransformableView notification, float transformationAmount);
+
+ /**
* Transform to this view from the given view
+ *
* @param notification the view to transform from
*/
void transformFrom(TransformableView notification);
/**
+ * Transform to this view from the given view by a specified amount.
+ *
+ * @param notification the view to transform from
+ * @param transformationAmount how much transformation should be done
+ */
+ void transformFrom(TransformableView notification, float transformationAmount);
+
+ /**
* Set this view to be fully visible or gone
+ *
* @param visible
*/
void setVisible(boolean visible);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index 63ff5aa..bf05d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -16,13 +16,17 @@
package com.android.systemui.statusbar;
-import android.os.Handler;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.util.ArrayMap;
import android.view.View;
import android.view.ViewGroup;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.TransformState;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.util.Stack;
@@ -33,9 +37,9 @@
private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
- private final Handler mHandler = new Handler();
private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>();
private ArrayMap<Integer, CustomTransformation> mCustomTransformations = new ArrayMap<>();
+ private ValueAnimator mViewTransformationAnimation;
public void addTransformedView(int key, View transformedView) {
mTransformedViews.put(key, transformedView);
@@ -59,61 +63,123 @@
}
@Override
- public void transformTo(TransformableView notification, Runnable endRunnable) {
- Runnable runnable = endRunnable;
+ public void transformTo(final TransformableView notification, final Runnable endRunnable) {
+ if (mViewTransformationAnimation != null) {
+ mViewTransformationAnimation.cancel();
+ }
+ mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ transformTo(notification, animation.getAnimatedFraction());
+ }
+ });
+ mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
+ mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ if (endRunnable != null) {
+ mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
+ public boolean mCancelled;
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ endRunnable.run();
+ if (!mCancelled) {
+ setVisible(false);
+ } else {
+ abortTransformations();
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+ });
+ }
+ mViewTransformationAnimation.start();
+ }
+
+ @Override
+ public void transformTo(TransformableView notification, float transformationAmount) {
for (Integer viewType : mTransformedViews.keySet()) {
TransformState ownState = getCurrentState(viewType);
if (ownState != null) {
CustomTransformation customTransformation = mCustomTransformations.get(viewType);
if (customTransformation != null && customTransformation.transformTo(
- ownState, notification, runnable)) {
+ ownState, notification, transformationAmount)) {
ownState.recycle();
- runnable = null;
continue;
}
TransformState otherState = notification.getCurrentState(viewType);
if (otherState != null) {
- boolean run = ownState.transformViewTo(otherState, runnable);
+ ownState.transformViewTo(otherState, transformationAmount);
otherState.recycle();
- if (run) {
- runnable = null;
- }
} else {
// there's no other view available
- CrossFadeHelper.fadeOut(mTransformedViews.get(viewType), runnable);
- runnable = null;
+ CrossFadeHelper.fadeOut(mTransformedViews.get(viewType), transformationAmount);
}
ownState.recycle();
}
}
- if (runnable != null) {
- // We need to post, since the visible type is only set after the transformation is
- // started
- mHandler.post(runnable);
- }
}
@Override
- public void transformFrom(TransformableView notification) {
+ public void transformFrom(final TransformableView notification) {
+ if (mViewTransformationAnimation != null) {
+ mViewTransformationAnimation.cancel();
+ }
+ mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ transformFrom(notification, animation.getAnimatedFraction());
+ }
+ });
+ mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
+ public boolean mCancelled;
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (!mCancelled) {
+ setVisible(true);
+ } else {
+ abortTransformations();
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+ });
+ mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
+ mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ mViewTransformationAnimation.start();
+ }
+
+ @Override
+ public void transformFrom(TransformableView notification, float transformationAmount) {
for (Integer viewType : mTransformedViews.keySet()) {
TransformState ownState = getCurrentState(viewType);
if (ownState != null) {
CustomTransformation customTransformation = mCustomTransformations.get(viewType);
if (customTransformation != null && customTransformation.transformFrom(
- ownState, notification)) {
+ ownState, notification, transformationAmount)) {
ownState.recycle();
continue;
}
TransformState otherState = notification.getCurrentState(viewType);
if (otherState != null) {
- ownState.transformViewFrom(otherState);
+ ownState.transformViewFrom(otherState, transformationAmount);
otherState.recycle();
} else {
// There's no other view, lets fade us in
// Certain views need to prepare the fade in and make sure its children are
// completely visible. An example is the notification header.
- ownState.prepareFadeIn();
- CrossFadeHelper.fadeIn(mTransformedViews.get(viewType));
+ if (transformationAmount == 0.0f) {
+ ownState.prepareFadeIn();
+ }
+ CrossFadeHelper.fadeIn(mTransformedViews.get(viewType), transformationAmount);
}
ownState.recycle();
}
@@ -131,6 +197,16 @@
}
}
+ private void abortTransformations() {
+ for (Integer viewType : mTransformedViews.keySet()) {
+ TransformState ownState = getCurrentState(viewType);
+ if (ownState != null) {
+ ownState.abortTransformation();
+ ownState.recycle();
+ }
+ }
+ }
+
/**
* Add the remaining transformation views such that all views are being transformed correctly
* @param viewRoot the root below which all elements need to be transformed
@@ -173,22 +249,44 @@
}
}
- public interface CustomTransformation {
+ public static abstract class CustomTransformation {
/**
* Transform a state to the given view
* @param ownState the state to transform
* @param notification the view to transform to
+ * @param transformationAmount how much transformation should be done
* @return whether a custom transformation is performed
*/
- boolean transformTo(TransformState ownState, TransformableView notification,
- Runnable endRunnable);
+ public abstract boolean transformTo(TransformState ownState,
+ TransformableView notification,
+ float transformationAmount);
/**
* Transform to this state from the given view
* @param ownState the state to transform to
* @param notification the view to transform from
+ * @param transformationAmount how much transformation should be done
* @return whether a custom transformation is performed
*/
- boolean transformFrom(TransformState ownState, TransformableView notification);
+ public abstract boolean transformFrom(TransformState ownState,
+ TransformableView notification,
+ float transformationAmount);
+
+ /**
+ * Perform a custom initialisation before transforming.
+ *
+ * @param ownState our own state
+ * @param otherState the other state
+ * @return whether a custom initialization is done
+ */
+ public boolean initTransformation(TransformState ownState,
+ TransformState otherState) {
+ return false;
+ }
+
+ public boolean customTransformTarget(TransformState ownState,
+ TransformState otherState) {
+ return false;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java
index 81483c6..b66e9f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeaderTransformState.java
@@ -46,7 +46,7 @@
}
@Override
- public boolean transformViewTo(TransformState otherState, Runnable endRunnable) {
+ public boolean transformViewTo(TransformState otherState, float transformationAmount) {
// if the transforming notification has a header, we have ensured that it looks the same
// but the expand button, so lets fade just that one and transform the work profile icon.
if (!(mTransformedView instanceof NotificationHeaderView)) {
@@ -62,14 +62,14 @@
if (headerChild != mExpandButton) {
headerChild.setVisibility(View.INVISIBLE);
} else {
- CrossFadeHelper.fadeOut(mExpandButton, endRunnable);
+ CrossFadeHelper.fadeOut(mExpandButton, transformationAmount);
}
}
return true;
}
@Override
- public void transformViewFrom(TransformState otherState) {
+ public void transformViewFrom(TransformState otherState, float transformationAmount) {
// if the transforming notification has a header, we have ensured that it looks the same
// but the expand button, so lets fade just that one and transform the work profile icon.
if (!(mTransformedView instanceof NotificationHeaderView)) {
@@ -85,12 +85,13 @@
continue;
}
if (headerChild == mExpandButton) {
- CrossFadeHelper.fadeIn(mExpandButton);
+ CrossFadeHelper.fadeIn(mExpandButton, transformationAmount);
} else {
headerChild.setVisibility(View.VISIBLE);
if (headerChild == mWorkProfileIcon) {
- mWorkProfileState.animateViewFrom(
- ((HeaderTransformState) otherState).mWorkProfileState);
+ mWorkProfileState.transformViewFullyFrom(
+ ((HeaderTransformState) otherState).mWorkProfileState,
+ transformationAmount);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
index 81144d5..c80cad8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
@@ -71,13 +71,13 @@
new ViewTransformationHelper.CustomTransformation() {
@Override
public boolean transformTo(TransformState ownState, TransformableView notification,
- Runnable endRunnable) {
+ float transformationAmount) {
// We want to transform to the same y location as the title
TransformState otherState = notification.getCurrentState(
TRANSFORMING_VIEW_TITLE);
- CrossFadeHelper.fadeOut(mTextView, endRunnable);
+ CrossFadeHelper.fadeOut(mTextView, transformationAmount);
if (otherState != null) {
- ownState.animateViewVerticalTo(otherState, endRunnable);
+ ownState.transformViewVerticalTo(otherState, transformationAmount);
otherState.recycle();
}
return true;
@@ -85,13 +85,13 @@
@Override
public boolean transformFrom(TransformState ownState,
- TransformableView notification) {
+ TransformableView notification, float transformationAmount) {
// We want to transform from the same y location as the title
TransformState otherState = notification.getCurrentState(
TRANSFORMING_VIEW_TITLE);
- CrossFadeHelper.fadeIn(mTextView);
+ CrossFadeHelper.fadeIn(mTextView, transformationAmount);
if (otherState != null) {
- ownState.animateViewVerticalFrom(otherState);
+ ownState.transformViewVerticalFrom(otherState, transformationAmount);
otherState.recycle();
}
return true;
@@ -133,11 +133,21 @@
}
@Override
+ public void transformTo(TransformableView notification, float transformationAmount) {
+ mTransformationHelper.transformTo(notification, transformationAmount);
+ }
+
+ @Override
public void transformFrom(TransformableView notification) {
mTransformationHelper.transformFrom(notification);
}
@Override
+ public void transformFrom(TransformableView notification, float transformationAmount) {
+ mTransformationHelper.transformFrom(notification, transformationAmount);
+ }
+
+ @Override
public void setVisible(boolean visible) {
setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
mTransformationHelper.setVisible(visible);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
index e891a97..45027c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -62,7 +62,7 @@
}
@Override
- protected boolean animateScale() {
+ protected boolean transformScale() {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
index 5a71caf..000f957 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java
@@ -142,7 +142,8 @@
protected void updateTransformedTypes() {
mTransformationHelper.reset();
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_HEADER, mNotificationHeader);
+ mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_HEADER,
+ mNotificationHeader);
}
@Override
@@ -299,11 +300,21 @@
}
@Override
+ public void transformTo(TransformableView notification, float transformationAmount) {
+ mTransformationHelper.transformTo(notification, transformationAmount);
+ }
+
+ @Override
public void transformFrom(TransformableView notification) {
mTransformationHelper.transformFrom(notification);
}
@Override
+ public void transformFrom(TransformableView notification, float transformationAmount) {
+ mTransformationHelper.transformFrom(notification, transformationAmount);
+ }
+
+ @Override
public void setVisible(boolean visible) {
super.setVisible(visible);
mTransformationHelper.setVisible(visible);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index 0c21f0b..fd4eca8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -49,76 +49,65 @@
new ViewTransformationHelper.CustomTransformation() {
@Override
public boolean transformTo(TransformState ownState,
- TransformableView notification, final Runnable endRunnable) {
+ TransformableView notification, final float transformationAmount) {
if (!(notification instanceof HybridNotificationView)) {
return false;
}
TransformState otherState = notification.getCurrentState(
TRANSFORMING_VIEW_TITLE);
final View text = ownState.getTransformedView();
- CrossFadeHelper.fadeOut(text, endRunnable);
+ CrossFadeHelper.fadeOut(text, transformationAmount);
if (otherState != null) {
- int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
- int[] ownPosition = ownState.getLaidOutLocationOnScreen();
- text.animate()
- .translationY((otherStablePosition[1]
- + otherState.getTransformedView().getHeight()
- - ownPosition[1]) * 0.33f)
- .setDuration(
- StackStateAnimator.ANIMATION_DURATION_STANDARD)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- if (endRunnable != null) {
- endRunnable.run();
- }
- TransformState.setClippingDeactivated(text,
- false);
- }
- });
- TransformState.setClippingDeactivated(text, true);
+ ownState.transformViewVerticalTo(otherState, this,
+ transformationAmount);
otherState.recycle();
}
return true;
}
@Override
+ public boolean customTransformTarget(TransformState ownState,
+ TransformState otherState) {
+ float endY = getTransformationY(ownState, otherState);
+ ownState.setTransformationEndY(endY);
+ return true;
+ }
+
+ @Override
public boolean transformFrom(TransformState ownState,
- TransformableView notification) {
+ TransformableView notification, float transformationAmount) {
if (!(notification instanceof HybridNotificationView)) {
return false;
}
TransformState otherState = notification.getCurrentState(
TRANSFORMING_VIEW_TITLE);
final View text = ownState.getTransformedView();
- boolean isVisible = text.getVisibility() == View.VISIBLE;
- CrossFadeHelper.fadeIn(text);
+ CrossFadeHelper.fadeIn(text, transformationAmount);
if (otherState != null) {
- int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
- int[] ownStablePosition = ownState.getLaidOutLocationOnScreen();
- if (!isVisible) {
- text.setTranslationY((otherStablePosition[1]
- + otherState.getTransformedView().getHeight()
- - ownStablePosition[1]) * 0.33f);
- }
- text.animate()
- .translationY(0)
- .setDuration(
- StackStateAnimator.ANIMATION_DURATION_STANDARD)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- TransformState.setClippingDeactivated(text,
- false);
- }
- });
- TransformState.setClippingDeactivated(text, true);
+ ownState.transformViewVerticalFrom(otherState, this,
+ transformationAmount);
otherState.recycle();
}
return true;
}
+
+ @Override
+ public boolean initTransformation(TransformState ownState,
+ TransformState otherState) {
+ float startY = getTransformationY(ownState, otherState);
+ ownState.setTransformationStartY(startY);
+ return true;
+ }
+
+ private float getTransformationY(TransformState ownState,
+ TransformState otherState) {
+ int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
+ int[] ownStablePosition = ownState.getLaidOutLocationOnScreen();
+ return (otherStablePosition[1]
+ + otherState.getTransformedView().getHeight()
+ - ownStablePosition[1]) * 0.33f;
+ }
+
}, TRANSFORMING_VIEW_TEXT);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
index 7089b78..4738657 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -34,4 +34,8 @@
v.setTag(R.id.icon_is_grayscale, grayscale);
return grayscale;
}
+
+ public static float interpolate(float start, float end, float amount) {
+ return start * (1.0f - amount) + end * amount;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 328f8b5..d3503e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -98,12 +98,22 @@
}
@Override
+ public void transformTo(TransformableView notification, float transformationAmount) {
+ CrossFadeHelper.fadeOut(mView, transformationAmount);
+ }
+
+ @Override
public void transformFrom(TransformableView notification) {
// By default we are fading in completely
CrossFadeHelper.fadeIn(mView);
}
@Override
+ public void transformFrom(TransformableView notification, float transformationAmount) {
+ CrossFadeHelper.fadeIn(mView, transformationAmount);
+ }
+
+ @Override
public void setVisible(boolean visible) {
mView.animate().cancel();
mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 67d31be..f04fe5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -30,23 +30,30 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
-import com.android.systemui.statusbar.stack.StackStateAnimator;
+import com.android.systemui.statusbar.ViewTransformationHelper;
/**
* A transform state of a view.
*/
public class TransformState {
- private static final int ANIMATE_X = 0x1;
- private static final int ANIMATE_Y = 0x10;
- private static final int ANIMATE_ALL = ANIMATE_X | ANIMATE_Y;
+ private static final float UNDEFINED = -1f;
+ private static final int TRANSOFORM_X = 0x1;
+ private static final int TRANSOFORM_Y = 0x10;
+ private static final int TRANSOFORM_ALL = TRANSOFORM_X | TRANSOFORM_Y;
private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
+ private static final int TRANSFORMATION_START_X = R.id.transformation_start_x_tag;
+ private static final int TRANSFORMATION_START_Y = R.id.transformation_start_y_tag;
+ private static final int TRANSFORMATION_START_SCLALE_X = R.id.transformation_start_scale_x_tag;
+ private static final int TRANSFORMATION_START_SCLALE_Y = R.id.transformation_start_scale_y_tag;
private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40);
protected View mTransformedView;
private int[] mOwnPosition = new int[2];
+ private float mTransformationEndY = UNDEFINED;
+ private float mTransformationEndX = UNDEFINED;
public void initFrom(View view) {
mTransformedView = view;
@@ -55,129 +62,233 @@
/**
* Transforms the {@link #mTransformedView} from the given transformviewstate
* @param otherState the state to transform from
+ * @param transformationAmount how much to transform
*/
- public void transformViewFrom(TransformState otherState) {
+ public void transformViewFrom(TransformState otherState, float transformationAmount) {
mTransformedView.animate().cancel();
if (sameAs(otherState)) {
- // We have the same content, lets show ourselves
- mTransformedView.setAlpha(1.0f);
- mTransformedView.setVisibility(View.VISIBLE);
+ if (mTransformedView.getVisibility() == View.INVISIBLE) {
+ // We have the same content, lets show ourselves
+ mTransformedView.setAlpha(1.0f);
+ mTransformedView.setVisibility(View.VISIBLE);
+ }
} else {
- CrossFadeHelper.fadeIn(mTransformedView);
+ CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
}
- animateViewFrom(otherState);
+ transformViewFullyFrom(otherState, transformationAmount);
}
- public void animateViewFrom(TransformState otherState) {
- animateViewFrom(otherState, ANIMATE_ALL);
+ public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
+ transformViewFrom(otherState, TRANSOFORM_ALL, null, transformationAmount);
}
- public void animateViewVerticalFrom(TransformState otherState) {
- animateViewFrom(otherState, ANIMATE_Y);
+ public void transformViewVerticalFrom(TransformState otherState,
+ ViewTransformationHelper.CustomTransformation customTransformation,
+ float transformationAmount) {
+ transformViewFrom(otherState, TRANSOFORM_Y, customTransformation, transformationAmount);
}
- private void animateViewFrom(TransformState otherState, int animationFlags) {
+ public void transformViewVerticalFrom(TransformState otherState, float transformationAmount) {
+ transformViewFrom(otherState, TRANSOFORM_Y, null, transformationAmount);
+ }
+
+ private void transformViewFrom(TransformState otherState, int transformationFlags,
+ ViewTransformationHelper.CustomTransformation customTransformation,
+ float transformationAmount) {
final View transformedView = mTransformedView;
+ boolean transformX = (transformationFlags & TRANSOFORM_X) != 0;
+ boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0;
+ boolean transformScale = transformScale();
// lets animate the positions correctly
- int[] otherPosition = otherState.getLocationOnScreen();
- int[] ownStablePosition = getLaidOutLocationOnScreen();
- if ((animationFlags & ANIMATE_X) != 0) {
- transformedView.setTranslationX(otherPosition[0] - ownStablePosition[0]);
- transformedView.animate().translationX(0);
- }
- if ((animationFlags & ANIMATE_Y) != 0) {
- transformedView.setTranslationY(otherPosition[1] - ownStablePosition[1]);
- transformedView.animate().translationY(0);
- }
- if (animateScale()) {
- // we also want to animate the scale if we're the same
- View otherView = otherState.getTransformedView();
- if (otherView.getWidth() != transformedView.getWidth()) {
- float scaleX = (otherView.getWidth() * otherView.getScaleX()
- / (float) transformedView.getWidth());
- transformedView.setScaleX(scaleX);
- transformedView.setPivotX(0);
- transformedView.animate().scaleX(1.0f);
+ if (transformationAmount == 0.0f) {
+ int[] otherPosition = otherState.getLocationOnScreen();
+ int[] ownStablePosition = getLaidOutLocationOnScreen();
+ if (customTransformation == null
+ || !customTransformation.initTransformation(this, otherState)) {
+ if (transformX) {
+ setTransformationStartX(otherPosition[0] - ownStablePosition[0]);
+ }
+ if (transformY) {
+ setTransformationStartY(otherPosition[1] - ownStablePosition[1]);
+ }
+ // we also want to animate the scale if we're the same
+ View otherView = otherState.getTransformedView();
+ if (transformScale && otherView.getWidth() != transformedView.getWidth()) {
+ setTransformationStartScaleX(otherView.getWidth() * otherView.getScaleX()
+ / (float) transformedView.getWidth());
+ transformedView.setPivotX(0);
+ } else {
+ setTransformationStartScaleX(UNDEFINED);
+ }
+ if (transformScale && otherView.getHeight() != transformedView.getHeight()) {
+ setTransformationStartScaleY(otherView.getHeight() * otherView.getScaleY()
+ / (float) transformedView.getHeight());
+ transformedView.setPivotY(0);
+ } else {
+ setTransformationStartScaleY(UNDEFINED);
+ }
}
- if (otherView.getHeight() != transformedView.getHeight()) {
- float scaleY = (otherView.getHeight() * otherView.getScaleY()
- / (float) transformedView.getHeight());
- transformedView.setScaleY(scaleY);
- transformedView.setPivotY(0);
- transformedView.animate().scaleY(1.0f);
+ if (!transformX) {
+ setTransformationStartX(UNDEFINED);
+ }
+ if (!transformY) {
+ setTransformationStartY(UNDEFINED);
+ }
+ if (!transformScale) {
+ setTransformationStartScaleX(UNDEFINED);
+ setTransformationStartScaleY(UNDEFINED);
+ }
+ setClippingDeactivated(transformedView, true);
+ }
+ float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ transformationAmount);
+ if (transformX) {
+ transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
+ 0.0f,
+ interpolatedValue));
+ }
+ if (transformY) {
+ transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
+ 0.0f,
+ interpolatedValue));
+ }
+ if (transformScale) {
+ float transformationStartScaleX = getTransformationStartScaleX();
+ if (transformationStartScaleX != UNDEFINED) {
+ transformedView.setScaleX(
+ NotificationUtils.interpolate(transformationStartScaleX,
+ 1.0f,
+ interpolatedValue));
+ }
+ float transformationStartScaleY = getTransformationStartScaleY();
+ if (transformationStartScaleY != UNDEFINED) {
+ transformedView.setScaleY(
+ NotificationUtils.interpolate(transformationStartScaleY,
+ 1.0f,
+ interpolatedValue));
}
}
- transformedView.animate()
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- setClippingDeactivated(transformedView, false);
- }
- });
- setClippingDeactivated(transformedView, true);
}
- protected boolean animateScale() {
+ protected boolean transformScale() {
return false;
}
/**
* Transforms the {@link #mTransformedView} to the given transformviewstate
* @param otherState the state to transform from
- * @param endRunnable a runnable to run at the end of the animation
+ * @param transformationAmount how much to transform
* @return whether an animation was started
*/
- public boolean transformViewTo(TransformState otherState, final Runnable endRunnable) {
+ public boolean transformViewTo(TransformState otherState, float transformationAmount) {
mTransformedView.animate().cancel();
if (sameAs(otherState)) {
// We have the same text, lets show ourselfs
- mTransformedView.setAlpha(0.0f);
- mTransformedView.setVisibility(View.INVISIBLE);
+ if (mTransformedView.getVisibility() == View.VISIBLE) {
+ mTransformedView.setAlpha(0.0f);
+ mTransformedView.setVisibility(View.INVISIBLE);
+ }
return false;
} else {
- CrossFadeHelper.fadeOut(mTransformedView, endRunnable);
+ CrossFadeHelper.fadeOut(mTransformedView, transformationAmount);
}
- animateViewTo(otherState, endRunnable);
+ transformViewFullyTo(otherState, transformationAmount);
return true;
}
- public void animateViewTo(TransformState otherState, Runnable endRunnable) {
- animateViewTo(otherState, endRunnable, ANIMATE_ALL);
+ public void transformViewFullyTo(TransformState otherState, float transformationAmount) {
+ transformViewTo(otherState, TRANSOFORM_ALL, null, transformationAmount);
}
- public void animateViewVerticalTo(TransformState otherState, Runnable endRunnable) {
- animateViewTo(otherState, endRunnable, ANIMATE_Y);
+ public void transformViewVerticalTo(TransformState otherState,
+ ViewTransformationHelper.CustomTransformation customTransformation,
+ float transformationAmount) {
+ transformViewTo(otherState, TRANSOFORM_Y, customTransformation, transformationAmount);
}
- private void animateViewTo(TransformState otherState, final Runnable endRunnable,
- int animationFlags) {
+ public void transformViewVerticalTo(TransformState otherState, float transformationAmount) {
+ transformViewTo(otherState, TRANSOFORM_Y, null, transformationAmount);
+ }
+
+ private void transformViewTo(TransformState otherState, int transformationFlags,
+ ViewTransformationHelper.CustomTransformation customTransformation,
+ float transformationAmount) {
// lets animate the positions correctly
+
+ final View transformedView = mTransformedView;
+ boolean transformX = (transformationFlags & TRANSOFORM_X) != 0;
+ boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0;
+ boolean transformScale = transformScale();
+ // lets animate the positions correctly
+ if (transformationAmount == 0.0f) {
+ if (transformX) {
+ float transformationStartX = getTransformationStartX();
+ float start = transformationStartX != UNDEFINED ? transformationStartX
+ : transformedView.getTranslationX();
+ setTransformationStartX(start);
+ }
+ if (transformY) {
+ float transformationStartY = getTransformationStartY();
+ float start = transformationStartY != UNDEFINED ? transformationStartY
+ : transformedView.getTranslationY();
+ setTransformationStartY(start);
+ }
+ View otherView = otherState.getTransformedView();
+ if (transformScale && otherView.getWidth() != transformedView.getWidth()) {
+ setTransformationStartScaleX(transformedView.getScaleX());
+ transformedView.setPivotX(0);
+ } else {
+ setTransformationStartScaleX(UNDEFINED);
+ }
+ if (transformScale && otherView.getHeight() != transformedView.getHeight()) {
+ setTransformationStartScaleY(transformedView.getScaleY());
+ transformedView.setPivotY(0);
+ } else {
+ setTransformationStartScaleY(UNDEFINED);
+ }
+ setClippingDeactivated(transformedView, true);
+ }
+ float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ transformationAmount);
int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
int[] ownPosition = getLaidOutLocationOnScreen();
- final View transformedView = mTransformedView;
- if ((animationFlags & ANIMATE_X) != 0) {
- transformedView.animate()
- .translationX(otherStablePosition[0] - ownPosition[0]);
+ if (transformX) {
+ float endX = otherStablePosition[0] - ownPosition[0];
+ if (customTransformation != null
+ && customTransformation.customTransformTarget(this, otherState)) {
+ endX = mTransformationEndX;
+ }
+ transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
+ endX,
+ interpolatedValue));
}
- if ((animationFlags & ANIMATE_Y) != 0) {
- transformedView.animate()
- .translationY(otherStablePosition[1] - ownPosition[1]);
+ if (transformY) {
+ float endY = otherStablePosition[1] - ownPosition[1];
+ if (customTransformation != null
+ && customTransformation.customTransformTarget(this, otherState)) {
+ endY = mTransformationEndY;
+ }
+ transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
+ endY,
+ interpolatedValue));
}
- transformedView.animate()
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- if (endRunnable != null) {
- endRunnable.run();
- }
- setClippingDeactivated(transformedView, false);
- }
- });
- setClippingDeactivated(transformedView, true);
+ if (transformScale) {
+ View otherView = otherState.getTransformedView();
+ float transformationStartScaleX = getTransformationStartScaleX();
+ if (transformationStartScaleX != UNDEFINED) {
+ transformedView.setScaleX(
+ NotificationUtils.interpolate(transformationStartScaleX,
+ (otherView.getWidth() / (float) transformedView.getWidth()),
+ interpolatedValue));
+ }
+ float transformationStartScaleY = getTransformationStartScaleY();
+ if (transformationStartScaleY != UNDEFINED) {
+ transformedView.setScaleY(
+ NotificationUtils.interpolate(transformationStartScaleY,
+ (otherView.getHeight() / (float) transformedView.getHeight()),
+ interpolatedValue));
+ }
+ }
}
public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
@@ -281,8 +392,54 @@
}
}
+ public void setTransformationEndY(float transformationEndY) {
+ mTransformationEndY = transformationEndY;
+ }
+
+ public void setTransformationEndX(float transformationEndX) {
+ mTransformationEndX = transformationEndX;
+ }
+
+ public float getTransformationStartX() {
+ Object tag = mTransformedView.getTag(TRANSFORMATION_START_X);
+ return tag == null ? UNDEFINED : (float) tag;
+ }
+
+ public float getTransformationStartY() {
+ Object tag = mTransformedView.getTag(TRANSFORMATION_START_Y);
+ return tag == null ? UNDEFINED : (float) tag;
+ }
+
+ public float getTransformationStartScaleX() {
+ Object tag = mTransformedView.getTag(TRANSFORMATION_START_SCLALE_X);
+ return tag == null ? UNDEFINED : (float) tag;
+ }
+
+ public float getTransformationStartScaleY() {
+ Object tag = mTransformedView.getTag(TRANSFORMATION_START_SCLALE_Y);
+ return tag == null ? UNDEFINED : (float) tag;
+ }
+
+ public void setTransformationStartX(float transformationStartX) {
+ mTransformedView.setTag(TRANSFORMATION_START_X, transformationStartX);
+ }
+
+ public void setTransformationStartY(float transformationStartY) {
+ mTransformedView.setTag(TRANSFORMATION_START_Y, transformationStartY);
+ }
+
+ private void setTransformationStartScaleX(float startScaleX) {
+ mTransformedView.setTag(TRANSFORMATION_START_SCLALE_X, startScaleX);
+ }
+
+ private void setTransformationStartScaleY(float startScaleY) {
+ mTransformedView.setTag(TRANSFORMATION_START_SCLALE_Y, startScaleY);
+ }
+
protected void reset() {
mTransformedView = null;
+ mTransformationEndX = UNDEFINED;
+ mTransformationEndY = UNDEFINED;
}
public void setVisible(boolean visible) {
@@ -306,6 +463,15 @@
mTransformedView.setTranslationY(0);
mTransformedView.setScaleX(1.0f);
mTransformedView.setScaleY(1.0f);
+ setClippingDeactivated(mTransformedView, false);
+ abortTransformation();
+ }
+
+ public void abortTransformation() {
+ mTransformedView.setTag(TRANSFORMATION_START_X, UNDEFINED);
+ mTransformedView.setTag(TRANSFORMATION_START_Y, UNDEFINED);
+ mTransformedView.setTag(TRANSFORMATION_START_SCLALE_X, UNDEFINED);
+ mTransformedView.setTag(TRANSFORMATION_START_SCLALE_Y, UNDEFINED);
}
public static TransformState obtain() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index d25e99b..49d5311 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -4089,7 +4089,7 @@
ExpandableNotificationRow row = null;
if (expandView instanceof ExpandableNotificationRow) {
row = (ExpandableNotificationRow) expandView;
- row.setUserExpanded(true);
+ row.setUserExpanded(true /* userExpanded */, true /* allowChildExpansion */);
}
boolean fullShadeNeedsBouncer = !userAllowsPrivateNotificationsInPublic(mCurrentUserId)
|| !mShowLockscreenNotifications || mFalsingManager.shouldEnforceBouncer();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 3bd68a9..ab34768 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -80,13 +80,13 @@
// Original is slightly larger than the mirror, so make sure to use the center for the
// positioning.
- int originalX = mInt2Cache[0] + original.getWidth()/2;
- int originalY = mInt2Cache[1];
+ int originalX = mInt2Cache[0] + original.getWidth() / 2;
+ int originalY = mInt2Cache[1] + original.getHeight() / 2;
mBrightnessMirror.setTranslationX(0);
mBrightnessMirror.setTranslationY(0);
mBrightnessMirror.getLocationInWindow(mInt2Cache);
- int mirrorX = mInt2Cache[0] + mBrightnessMirror.getWidth()/2;
- int mirrorY = mInt2Cache[1];
+ int mirrorX = mInt2Cache[0] + mBrightnessMirror.getWidth() / 2;
+ int mirrorY = mInt2Cache[1] + mBrightnessMirror.getHeight() / 2;
mBrightnessMirror.setTranslationX(originalX - mirrorX);
mBrightnessMirror.setTranslationY(originalY - mirrorY);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index 49aec42..030c8b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -29,6 +29,7 @@
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.HybridNotificationViewManager;
+import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.phone.NotificationPanelView;
import java.util.ArrayList;
@@ -60,6 +61,8 @@
private ViewState mGroupOverFlowState;
private int mRealHeight;
private int mLayoutDirection = LAYOUT_DIRECTION_UNDEFINED;
+ private boolean mUserLocked;
+ private int mActualHeight;
public NotificationChildrenContainer(Context context) {
this(context, null);
@@ -281,27 +284,45 @@
*/
private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
int intrinsicHeight = mNotificationHeaderHeight;
- if (mChildrenExpanded) {
- intrinsicHeight += mNotificatonTopPadding;
- }
int visibleChildren = 0;
int childCount = mChildren.size();
+ boolean firstChild = true;
+ float expandFactor = 0;
+ if (mUserLocked) {
+ expandFactor = getChildExpandFraction();
+ }
for (int i = 0; i < childCount; i++) {
if (visibleChildren >= maxAllowedVisibleChildren) {
break;
}
+ if (!firstChild) {
+ if (mUserLocked) {
+ intrinsicHeight += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
+ expandFactor);
+ } else {
+ intrinsicHeight += mChildrenExpanded ? mDividerHeight : mChildPadding;
+ }
+ } else {
+ if (mUserLocked) {
+ intrinsicHeight += NotificationUtils.interpolate(
+ 0,
+ mNotificatonTopPadding + mDividerHeight,
+ expandFactor);
+ } else {
+ intrinsicHeight += mChildrenExpanded
+ ? mNotificatonTopPadding + mDividerHeight
+ : 0;
+ }
+ firstChild = false;
+ }
ExpandableNotificationRow child = mChildren.get(i);
intrinsicHeight += child.getIntrinsicHeight();
visibleChildren++;
}
- if (visibleChildren > 0) {
- if (mChildrenExpanded) {
- intrinsicHeight += visibleChildren * mDividerHeight;
- } else {
- intrinsicHeight += (visibleChildren - 1) * mChildPadding;
- }
- }
- if (!mChildrenExpanded) {
+ if (mUserLocked) {
+ intrinsicHeight += NotificationUtils.interpolate(mCollapsedBottompadding, 0.0f,
+ expandFactor);
+ } else if (!mChildrenExpanded) {
intrinsicHeight += mCollapsedBottompadding;
}
return intrinsicHeight;
@@ -323,12 +344,28 @@
int lastVisibleIndex = hasOverflow
? maxAllowedVisibleChildren - 2
: maxAllowedVisibleChildren - 1;
+ float expandFactor = 0;
+ if (mUserLocked) {
+ expandFactor = getChildExpandFraction();
+ }
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
if (!firstChild) {
- yPosition += mChildrenExpanded ? mDividerHeight : mChildPadding;
+ if (mUserLocked) {
+ yPosition += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
+ expandFactor);
+ } else {
+ yPosition += mChildrenExpanded ? mDividerHeight : mChildPadding;
+ }
} else {
- yPosition += mChildrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0;
+ if (mUserLocked) {
+ yPosition += NotificationUtils.interpolate(
+ 0,
+ mNotificatonTopPadding + mDividerHeight,
+ expandFactor);
+ } else {
+ yPosition += mChildrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0;
+ }
firstChild = false;
}
StackViewState childState = resultState.getViewStateForView(child);
@@ -375,6 +412,7 @@
public void applyState(StackScrollState state) {
int childCount = mChildren.size();
ViewState tmpState = new ViewState();
+ float expandFraction = getChildExpandFraction();
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
StackViewState viewState = state.getViewStateForView(child);
@@ -384,7 +422,11 @@
View divider = mDividers.get(i);
tmpState.initFrom(divider);
tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
- tmpState.alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
+ float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
+ if (mUserLocked && viewState.alpha != 0) {
+ alpha = NotificationUtils.interpolate(0, 0.5f, expandFraction);
+ }
+ tmpState.alpha = alpha;
state.applyViewState(divider, tmpState);
}
if (mGroupOverflowContainer != null) {
@@ -407,6 +449,7 @@
long baseDelay, long duration) {
int childCount = mChildren.size();
ViewState tmpState = new ViewState();
+ float expandFraction = getChildExpandFraction();
for (int i = childCount - 1; i >= 0; i--) {
ExpandableNotificationRow child = mChildren.get(i);
StackViewState viewState = state.getViewStateForView(child);
@@ -416,7 +459,11 @@
View divider = mDividers.get(i);
tmpState.initFrom(divider);
tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
- tmpState.alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
+ float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
+ if (mUserLocked && viewState.alpha != 0) {
+ alpha = NotificationUtils.interpolate(0, 0.5f, expandFraction);
+ }
+ tmpState.alpha = alpha;
stateAnimator.startViewAnimations(divider, tmpState, baseDelay, duration);
}
if (mGroupOverflowContainer != null) {
@@ -449,7 +496,70 @@
}
public int getMaxContentHeight() {
- return getIntrinsicHeight(NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
+ int maxContentHeight = mNotificationHeaderHeight + mNotificatonTopPadding;
+ int visibleChildren = 0;
+ int childCount = mChildren.size();
+ for (int i = 0; i < childCount; i++) {
+ if (visibleChildren >= NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED) {
+ break;
+ }
+ ExpandableNotificationRow child = mChildren.get(i);
+ float childHeight = child.isExpanded()
+ ? child.getMaxExpandHeight()
+ : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
+ maxContentHeight += childHeight;
+ visibleChildren++;
+ }
+ if (visibleChildren > 0) {
+ maxContentHeight += visibleChildren * mDividerHeight;
+ }
+ return maxContentHeight;
+ }
+
+ public void setActualHeight(int actualHeight) {
+ if (!mUserLocked) {
+ return;
+ }
+ mActualHeight = actualHeight;
+ float fraction = getChildExpandFraction();
+ int childCount = mChildren.size();
+ for (int i = 0; i < childCount; i++) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ float childHeight = child.isExpanded()
+ ? child.getMaxExpandHeight()
+ : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
+ float singleLineHeight = child.getShowingLayout().getMinHeight(
+ false /* likeGroupExpanded */);
+ child.setActualHeight((int) NotificationUtils.interpolate(singleLineHeight, childHeight,
+ fraction), false);
+ }
+ }
+
+ public float getChildExpandFraction() {
+ int allChildrenVisibleHeight = getChildrenExpandStartHeight();
+ int maxContentHeight = getMaxContentHeight();
+ float factor = (mActualHeight - allChildrenVisibleHeight)
+ / (float) (maxContentHeight - allChildrenVisibleHeight);
+ return Math.max(0.0f, Math.min(1.0f, factor));
+ }
+
+ private int getChildrenExpandStartHeight() {
+ int intrinsicHeight = mNotificationHeaderHeight;
+ int visibleChildren = 0;
+ int childCount = mChildren.size();
+ for (int i = 0; i < childCount; i++) {
+ if (visibleChildren >= NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED) {
+ break;
+ }
+ ExpandableNotificationRow child = mChildren.get(i);
+ intrinsicHeight += child.getMinHeight();
+ visibleChildren++;
+ }
+ if (visibleChildren > 0) {
+ intrinsicHeight += (visibleChildren - 1) * mChildPadding;
+ }
+ intrinsicHeight += mCollapsedBottompadding;
+ return intrinsicHeight;
}
public int getMinHeight() {
@@ -477,4 +587,13 @@
mDividers.set(i, divider);
}
}
+
+ public void setUserLocked(boolean userLocked) {
+ mUserLocked = userLocked;
+ int childCount = mChildren.size();
+ for (int i = 0; i < childCount; i++) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ child.setUserLocked(userLocked);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index fe06c3a..340ebb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -64,6 +64,7 @@
import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.FakeShadowView;
+import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -538,7 +539,7 @@
ExpandableView child = (ExpandableView) getChildAt(i);
if (mChildrenToAddAnimated.contains(child)) {
int startingPosition = getPositionInLinearLayout(child);
- int padding = child.needsIncreasedPadding()
+ int padding = child.getIncreasedPaddingAmount() == 1.0f
? mIncreasedPaddingBetweenElements :
mPaddingBetweenElements;
int childHeight = getIntrinsicHeight(child) + padding;
@@ -1531,18 +1532,18 @@
private void updateContentHeight() {
int height = 0;
- boolean previousNeedsIncreasedPaddings = false;
+ float previousIncreasedAmount = 0.0f;
for (int i = 0; i < getChildCount(); i++) {
ExpandableView expandableView = (ExpandableView) getChildAt(i);
if (expandableView.getVisibility() != View.GONE) {
- boolean needsIncreasedPaddings = expandableView.needsIncreasedPadding();
+ float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
if (height != 0) {
- int padding = needsIncreasedPaddings || previousNeedsIncreasedPaddings
- ? mIncreasedPaddingBetweenElements
- : mPaddingBetweenElements;
- height += padding;
+ height += (int) NotificationUtils.interpolate(
+ mPaddingBetweenElements,
+ mIncreasedPaddingBetweenElements,
+ Math.max(previousIncreasedAmount, increasedPaddingAmount));
}
- previousNeedsIncreasedPaddings = needsIncreasedPaddings;
+ previousIncreasedAmount = increasedPaddingAmount;
height += expandableView.getIntrinsicHeight();
}
}
@@ -1813,8 +1814,12 @@
// it will be set once we reach the boundary
mMaxOverScroll = 0.0f;
}
+ int minScrollY = Math.max(0, scrollRange);
+ if (mExpandedInThisMotion) {
+ minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
+ }
mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
- Math.max(0, scrollRange), 0, Integer.MAX_VALUE / 2);
+ minScrollY, 0, mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
postInvalidateOnAnimation();
}
@@ -2097,9 +2102,10 @@
*/
private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
int startingPosition = getPositionInLinearLayout(removedChild);
- int padding = removedChild.needsIncreasedPadding()
- ? mIncreasedPaddingBetweenElements :
- mPaddingBetweenElements;
+ int padding = (int) NotificationUtils.interpolate(
+ mPaddingBetweenElements,
+ mIncreasedPaddingBetweenElements,
+ removedChild.getIncreasedPaddingAmount());
int childHeight = getIntrinsicHeight(removedChild) + padding;
int endPosition = startingPosition + childHeight;
if (endPosition <= mOwnScrollY) {
@@ -2123,19 +2129,19 @@
private int getPositionInLinearLayout(View requestedChild) {
int position = 0;
- boolean previousNeedsIncreasedPaddings = false;
+ float previousIncreasedAmount = 0.0f;
for (int i = 0; i < getChildCount(); i++) {
ExpandableView child = (ExpandableView) getChildAt(i);
boolean notGone = child.getVisibility() != View.GONE;
if (notGone) {
- boolean needsIncreasedPaddings = child.needsIncreasedPadding();
+ float increasedPaddingAmount = child.getIncreasedPaddingAmount();
if (position != 0) {
- int padding = needsIncreasedPaddings || previousNeedsIncreasedPaddings
- ? mIncreasedPaddingBetweenElements :
- mPaddingBetweenElements;
- position += padding;
+ position += (int) NotificationUtils.interpolate(
+ mPaddingBetweenElements,
+ mIncreasedPaddingBetweenElements,
+ Math.max(previousIncreasedAmount, increasedPaddingAmount));
}
- previousNeedsIncreasedPaddings = needsIncreasedPaddings;
+ previousIncreasedAmount = increasedPaddingAmount;
}
if (child == requestedChild) {
return position;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index d78d626..eea923f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -25,9 +25,10 @@
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.notification.FakeShadowView;
+import com.android.systemui.statusbar.notification.NotificationUtils;
import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.HashMap;
import java.util.List;
/**
@@ -297,18 +298,22 @@
int childCount = hostView.getChildCount();
state.visibleChildren.clear();
state.visibleChildren.ensureCapacity(childCount);
- state.increasedPaddingSet.clear();
+ state.increasedPaddingMap.clear();
int notGoneIndex = 0;
ExpandableView lastView = null;
for (int i = 0; i < childCount; i++) {
ExpandableView v = (ExpandableView) hostView.getChildAt(i);
if (v.getVisibility() != View.GONE) {
notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
- boolean needsIncreasedPadding = v.needsIncreasedPadding();
- if (needsIncreasedPadding) {
- state.increasedPaddingSet.add(v);
+ float increasedPadding = v.getIncreasedPaddingAmount();
+ if (increasedPadding != 0.0f) {
+ state.increasedPaddingMap.put(v, increasedPadding);
if (lastView != null) {
- state.increasedPaddingSet.add(lastView);
+ Float prevValue = state.increasedPaddingMap.get(lastView);
+ float newValue = prevValue != null
+ ? Math.max(prevValue, increasedPadding)
+ : increasedPadding;
+ state.increasedPaddingMap.put(lastView, newValue);
}
}
if (v instanceof ExpandableNotificationRow) {
@@ -423,9 +428,12 @@
private int getPaddingAfterChild(StackScrollAlgorithmState algorithmState,
ExpandableView child) {
- return algorithmState.increasedPaddingSet.contains(child)
- ? mIncreasedPaddingBetweenElements
- : mPaddingBetweenElements;
+ Float paddingValue = algorithmState.increasedPaddingMap.get(child);
+ return paddingValue == null
+ ? mPaddingBetweenElements
+ : (int) NotificationUtils.interpolate(mPaddingBetweenElements,
+ mIncreasedPaddingBetweenElements,
+ paddingValue);
}
private void updateHeadsUpStates(StackScrollState resultState,
@@ -765,9 +773,10 @@
public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>();
/**
- * The children from the host that need an increased padding after them.
+ * The children from the host that need an increased padding after them. A value of 0 means
+ * no increased padding, a value of 1 means full padding.
*/
- public final HashSet<ExpandableView> increasedPaddingSet = new HashSet<>();
+ public final HashMap<ExpandableView, Float> increasedPaddingMap = new HashMap<>();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
index ec49256..0925638 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -424,9 +424,7 @@
}
Intent intent = new Intent(mContext, PipMenuActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- final ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchStackId(PINNED_STACK_ID);
- mContext.startActivity(intent, options.toBundle());
+ mContext.startActivity(intent);
}
public void addListener(Listener listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
index 7e229d4..4171dbc 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
@@ -33,29 +33,62 @@
private final PipManager mPipManager = PipManager.getInstance();
private MediaController mMediaController;
+ private View mFullButtonView;
+ private View mFullDescriptionView;
+ private View mPlayPauseButtonView;
+ private View mPlayPauseDescriptionView;
+ private View mCloseButtonView;
+ private View mCloseDescriptionView;
+
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.tv_pip_menu);
mPipManager.addListener(this);
- findViewById(R.id.full).setOnClickListener(new View.OnClickListener() {
+ mFullButtonView = findViewById(R.id.full);
+ mFullDescriptionView = findViewById(R.id.full_desc);
+ mFullButtonView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPipManager.movePipToFullscreen();
+ finish();
}
});
- findViewById(R.id.exit).setOnClickListener(new View.OnClickListener() {
+ mFullButtonView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ mFullDescriptionView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE);
+ }
+ });
+
+ mPlayPauseButtonView = findViewById(R.id.play_pause);
+ mPlayPauseDescriptionView = findViewById(R.id.play_pause_desc);
+ mPlayPauseButtonView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // TODO: Implement play/pause.
+ }
+ });
+ mPlayPauseButtonView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ mPlayPauseDescriptionView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE);
+ }
+ });
+
+ mCloseButtonView = findViewById(R.id.close);
+ mCloseDescriptionView = findViewById(R.id.close_desc);
+ mCloseButtonView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPipManager.closePip();
finish();
}
});
- findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() {
+ mCloseButtonView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
- public void onClick(View v) {
- mPipManager.resizePinnedStack(PipManager.STATE_PIP_OVERLAY);
- finish();
+ public void onFocusChange(View v, boolean hasFocus) {
+ mCloseDescriptionView.setVisibility(hasFocus ? View.VISIBLE : View.INVISIBLE);
}
});
}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 964688b..c9c5805 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -29,9 +29,7 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
$(call all-Iaidl-files-under, src) \
- $(call all-java-files-under, ../src) \
- $(call all-proto-files-under, ../src) \
- src/com/android/systemui/EventLogTags.logtags
+ $(call all-java-files-under, ../src)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
frameworks/support/v7/preference/res \
@@ -53,7 +51,8 @@
android-support-v7-preference \
android-support-v7-appcompat \
android-support-v14-preference \
- android-support-v17-leanback
+ android-support-v17-leanback \
+ SystemUI-proto-tags
# sign this with platform cert, so this test is allowed to inject key events into
# UI it doesn't own. This is necessary to allow screenshots to be taken
diff --git a/packages/SystemUI/tests/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/tests/src/com/android/systemui/EventLogTags.logtags
deleted file mode 120000
index 2f243d7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/EventLogTags.logtags
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../src/com/android/systemui/EventLogTags.logtags
\ No newline at end of file
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index cd026a8..568edab 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -441,5 +441,14 @@
// Tuner: Change do not disturb volume buttons shortcut.
ACTION_TUNER_DO_NOT_DISTURB_VOLUME_SHORTCUT = 315;
+
+ // Logs the action the user takes when an app crashed.
+ ACTION_APP_CRASH = 316;
+
+ // Logs the action the user takes when an app ANR'd.
+ ACTION_APP_ANR = 317;
+
+ // Logged when a user double taps the overview button to launch the previous task
+ OVERVIEW_LAUNCH_PREVIOUS_TASK = 318;
}
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index d14364d..caeb0c6 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -660,12 +660,7 @@
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
- synchronized (accounts.cacheLock) {
- if (!accountExistsCacheLocked(accounts, account)) {
- return null;
- }
- return readUserDataInternalLocked(accounts, account, key);
- }
+ return readUserDataInternal(accounts, account, key);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -1717,58 +1712,44 @@
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
- synchronized (accounts.cacheLock) {
- if (!accountExistsCacheLocked(accounts, account)) {
- return;
- }
- setUserdataInternalLocked(accounts, account, key, value);
- }
+ setUserdataInternal(accounts, account, key, value);
} finally {
restoreCallingIdentity(identityToken);
}
}
- private boolean accountExistsCacheLocked(UserAccounts accounts, Account account) {
- if (accounts.accountCache.containsKey(account.type)) {
- for (Account acc : accounts.accountCache.get(account.type)) {
- if (acc.name.equals(account.name)) {
- return true;
- }
- }
- }
- return false;
- }
-
- private void setUserdataInternalLocked(UserAccounts accounts, Account account, String key,
+ private void setUserdataInternal(UserAccounts accounts, Account account, String key,
String value) {
if (account == null || key == null) {
return;
}
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- long accountId = getAccountIdLocked(db, account);
- if (accountId < 0) {
- return;
- }
- long extrasId = getExtrasIdLocked(db, accountId, key);
- if (extrasId < 0) {
- extrasId = insertExtraLocked(db, accountId, key, value);
- if (extrasId < 0) {
+ synchronized (accounts.cacheLock) {
+ final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ long accountId = getAccountIdLocked(db, account);
+ if (accountId < 0) {
return;
}
- } else {
- ContentValues values = new ContentValues();
- values.put(EXTRAS_VALUE, value);
- if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
- return;
- }
+ long extrasId = getExtrasIdLocked(db, accountId, key);
+ if (extrasId < 0 ) {
+ extrasId = insertExtraLocked(db, accountId, key, value);
+ if (extrasId < 0) {
+ return;
+ }
+ } else {
+ ContentValues values = new ContentValues();
+ values.put(EXTRAS_VALUE, value);
+ if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
+ return;
+ }
+ }
+ writeUserDataIntoCacheLocked(accounts, db, account, key, value);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
}
- writeUserDataIntoCacheLocked(accounts, db, account, key, value);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
}
}
@@ -4803,16 +4784,17 @@
}
}
- protected String readUserDataInternalLocked(
- UserAccounts accounts, Account account, String key) {
- HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
- if (userDataForAccount == null) {
- // need to populate the cache for this account
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
- userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
- accounts.userDataCache.put(account, userDataForAccount);
+ protected String readUserDataInternal(UserAccounts accounts, Account account, String key) {
+ synchronized (accounts.cacheLock) {
+ HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
+ if (userDataForAccount == null) {
+ // need to populate the cache for this account
+ final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+ userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
+ accounts.userDataCache.put(account, userDataForAccount);
+ }
+ return userDataForAccount.get(key);
}
- return userDataForAccount.get(key);
}
protected HashMap<String, String> readUserDataForAccountFromDatabaseLocked(
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 90a7da9..32ca5bf 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -321,6 +321,7 @@
pw.print(" visible="); pw.print(visible);
pw.print(" sleeping="); pw.print(sleeping);
pw.print(" idle="); pw.println(idle);
+ pw.print(" mStartingWindowShown="); pw.println(mStartingWindowShown);
pw.print(prefix); pw.print("fullscreen="); pw.print(fullscreen);
pw.print(" noDisplay="); pw.print(noDisplay);
pw.print(" immersive="); pw.print(immersive);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index acde10f..ec37667 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2263,6 +2263,7 @@
mService.compatibilityInfoForPackageLocked(next.info.applicationInfo),
next.nonLocalizedLabel, next.labelRes, next.icon, next.logo,
next.windowFlags, null, true);
+ next.mStartingWindowShown = true;
}
mStackSupervisor.startSpecificActivityLocked(next, true, false);
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
@@ -2295,6 +2296,7 @@
next.nonLocalizedLabel,
next.labelRes, next.icon, next.logo, next.windowFlags,
null, true);
+ next.mStartingWindowShown = true;
}
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);
}
diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java
index b746a4b..86cdbcc 100644
--- a/services/core/java/com/android/server/am/AppErrorDialog.java
+++ b/services/core/java/com/android/server/am/AppErrorDialog.java
@@ -37,6 +37,7 @@
import static com.android.server.am.ActivityManagerService.IS_USER_BUILD;
final class AppErrorDialog extends BaseErrorDialog implements View.OnClickListener {
+
private final ActivityManagerService mService;
private final AppErrorResult mResult;
private final ProcessRecord mProc;
@@ -44,12 +45,17 @@
private CharSequence mName;
+ static int CANT_SHOW = -1;
+ static int BACKGROUND_USER = -2;
+ static int ALREADY_SHOWING = -3;
+
// Event 'what' codes
static final int FORCE_QUIT = 1;
static final int FORCE_QUIT_AND_REPORT = 2;
static final int RESTART = 3;
static final int RESET = 4;
static final int MUTE = 5;
+ static final int TIMEOUT = 6;
// 5-minute timeout, then we automatically dismiss the crash dialog
static final long DISMISS_TIMEOUT = 1000 * 60 * 5;
@@ -89,7 +95,7 @@
// After the timeout, pretend the user clicked the quit button
mHandler.sendMessageDelayed(
- mHandler.obtainMessage(FORCE_QUIT),
+ mHandler.obtainMessage(TIMEOUT),
DISMISS_TIMEOUT);
}
@@ -132,7 +138,7 @@
mResult.set(result);
// Make sure we don't have time timeout still hanging around.
- removeMessages(FORCE_QUIT);
+ removeMessages(TIMEOUT);
dismiss();
}
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 190e9e1..055935d 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -17,6 +17,8 @@
package com.android.server.am;
import com.android.internal.app.ProcessMap;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto;
import com.android.internal.os.ProcessCpuTracker;
import com.android.server.Watchdog;
@@ -403,6 +405,10 @@
Intent appErrorIntent = null;
final long ident = Binder.clearCallingIdentity();
try {
+ MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_CRASH, res);
+ if (res == AppErrorDialog.TIMEOUT) {
+ res = AppErrorDialog.FORCE_QUIT;
+ }
if (res == AppErrorDialog.RESET) {
String[] packageList = r.getPackageList();
if (packageList != null) {
@@ -697,7 +703,7 @@
if (proc != null && proc.crashDialog != null) {
Slog.e(TAG, "App already has crash dialog: " + proc);
if (res != null) {
- res.set(0);
+ res.set(AppErrorDialog.ALREADY_SHOWING);
}
return;
}
@@ -710,7 +716,7 @@
if (isBackground && !showBackground) {
Slog.w(TAG, "Skipping crash dialog of " + proc + ": background");
if (res != null) {
- res.set(0);
+ res.set(AppErrorDialog.BACKGROUND_USER);
}
return;
}
@@ -724,7 +730,7 @@
// The device is asleep, so just pretend that the user
// saw a crash dialog and hit "force quit".
if (res != null) {
- res.set(0);
+ res.set(AppErrorDialog.CANT_SHOW);
}
}
}
@@ -920,6 +926,8 @@
ProcessRecord proc = (ProcessRecord)data.get("app");
if (proc != null && proc.anrDialog != null) {
Slog.e(TAG, "App already has anr dialog: " + proc);
+ MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
+ AppNotRespondingDialog.ALREADY_SHOWING);
return;
}
@@ -939,6 +947,8 @@
d.show();
proc.anrDialog = d;
} else {
+ MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
+ AppNotRespondingDialog.CANT_SHOW);
// Just kill the app if there is no dialog to be shown.
mService.killAppAtUsersRequest(proc, null);
}
diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
index 9875887..6d1d9f3 100644
--- a/services/core/java/com/android/server/am/AppNotRespondingDialog.java
+++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
@@ -16,6 +16,9 @@
package com.android.server.am;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto;
+
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
@@ -41,6 +44,9 @@
static final int WAIT = 2;
static final int WAIT_AND_REPORT = 3;
+ public static final int CANT_SHOW = -1;
+ public static final int ALREADY_SHOWING = -2;
+
private final ActivityManagerService mService;
private final ProcessRecord mProc;
@@ -132,6 +138,10 @@
private final Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
Intent appErrorIntent = null;
+
+ MetricsLogger.action(getContext(), MetricsProto.MetricsEvent.ACTION_APP_ANR,
+ msg.what);
+
switch (msg.what) {
case FORCE_CLOSE:
// Kill the application.
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index f1d7b36..536c75e 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -19,6 +19,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
@@ -54,6 +55,7 @@
import android.util.SparseBooleanArray;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.ArrayUtils;
import com.android.server.DeviceIdleController;
import com.android.server.LocalServices;
import com.android.server.job.JobStore.JobStatusFunctor;
@@ -66,6 +68,8 @@
import com.android.server.job.controllers.StateController;
import com.android.server.job.controllers.TimeController;
+import libcore.util.EmptyArray;
+
/**
* Responsible for taking jobs representing work to be performed by a client app, and determining
* based on the criteria specified when that job should be run against the client application's
@@ -142,7 +146,7 @@
*/
final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
- final ArrayList<Integer> mStartedUsers = new ArrayList<>();
+ int[] mStartedUsers = EmptyArray.INT;
final JobHandler mHandler;
final JobSchedulerStub mJobSchedulerStub;
@@ -228,14 +232,20 @@
@Override
public void onStartUser(int userHandle) {
- mStartedUsers.add(userHandle);
+ mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userHandle);
+ // Let's kick any outstanding jobs for this user.
+ mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ }
+
+ @Override
+ public void onUnlockUser(int userHandle) {
// Let's kick any outstanding jobs for this user.
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
}
@Override
public void onStopUser(int userHandle) {
- mStartedUsers.remove(Integer.valueOf(userHandle));
+ mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userHandle);
}
/**
@@ -936,18 +946,31 @@
* - It's not pending.
* - It's not already running on a JSC.
* - The user that requested the job is running.
+ * - The component is enabled and runnable.
*/
private boolean isReadyToBeExecutedLocked(JobStatus job) {
final boolean jobReady = job.isReady();
final boolean jobPending = mPendingJobs.contains(job);
final boolean jobActive = isCurrentlyActiveLocked(job);
- final boolean userRunning = mStartedUsers.contains(job.getUserId());
+
+ final int userId = job.getUserId();
+ final boolean userStarted = ArrayUtils.contains(mStartedUsers, userId);
+ final boolean componentPresent;
+ try {
+ componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
+ job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+ userId) != null);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+
if (DEBUG) {
Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString()
+ " ready=" + jobReady + " pending=" + jobPending
- + " active=" + jobActive + " userRunning=" + userRunning);
+ + " active=" + jobActive + " userStarted=" + userStarted
+ + " componentPresent=" + componentPresent);
}
- return userRunning && jobReady && !jobPending && !jobActive;
+ return userStarted && componentPresent && jobReady && !jobPending && !jobActive;
}
/**
@@ -1295,11 +1318,7 @@
void dumpInternal(final PrintWriter pw) {
final long now = SystemClock.elapsedRealtime();
synchronized (mLock) {
- pw.print("Started users: ");
- for (int i=0; i<mStartedUsers.size(); i++) {
- pw.print("u" + mStartedUsers.get(i) + " ");
- }
- pw.println();
+ pw.println("Started users: " + Arrays.toString(mStartedUsers));
pw.println("Registered jobs:");
if (mJobs.size() > 0) {
mJobs.forEachJob(new JobStatusFunctor() {
@@ -1319,7 +1338,7 @@
pw.print(" active=");
pw.print(isCurrentlyActiveLocked(job));
pw.print(" user=");
- pw.print(mStartedUsers.contains(job.getUserId()));
+ pw.print(ArrayUtils.contains(mStartedUsers, job.getUserId()));
pw.println(")");
}
});
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 862c061..29c54e9 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -77,6 +77,8 @@
*/
private static final int OPTIMISTIC_VOLUME_TIMEOUT = 1000;
+ private static final int UID_NOT_SET = -1;
+
private final MessageHandler mHandler;
private final int mOwnerPid;
@@ -122,6 +124,9 @@
private boolean mIsActive = false;
private boolean mDestroyed = false;
+ private int mCallingUid = UID_NOT_SET;
+ private String mCallingPackage;
+
public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
ISessionCallback cb, String tag, MediaSessionService service, Handler handler) {
mOwnerPid = ownerPid;
@@ -419,7 +424,9 @@
return mSessionCb.mCb;
}
- public void sendMediaButton(KeyEvent ke, int sequenceId, ResultReceiver cb) {
+ public void sendMediaButton(KeyEvent ke, int sequenceId,
+ ResultReceiver cb, int uid, String packageName) {
+ updateCallingPackage(uid, packageName);
mSessionCb.sendMediaButton(ke, sequenceId, cb);
}
@@ -680,6 +687,33 @@
return -1;
}
+ private void updateCallingPackage() {
+ updateCallingPackage(UID_NOT_SET, null);
+ }
+
+ private void updateCallingPackage(int uid, String packageName) {
+ if (uid == UID_NOT_SET) {
+ uid = Binder.getCallingUid();
+ }
+ synchronized (mLock) {
+ if (mCallingUid == UID_NOT_SET || mCallingUid != uid) {
+ mCallingUid = uid;
+ mCallingPackage = packageName;
+ if (mCallingPackage != null) {
+ return;
+ }
+ Context context = mService.getContext();
+ if (context == null) {
+ return;
+ }
+ String[] packages = context.getPackageManager().getPackagesForUid(uid);
+ if (packages != null && packages.length > 0) {
+ mCallingPackage = packages[0];
+ }
+ }
+ }
+ }
+
private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
@Override
public void run() {
@@ -831,6 +865,11 @@
mHandler.post(MessageHandler.MSG_UPDATE_VOLUME);
}
}
+
+ @Override
+ public String getCallingPackage() {
+ return mCallingPackage;
+ }
}
class SessionCb {
@@ -1025,11 +1064,13 @@
@Override
public void sendCommand(String command, Bundle args, ResultReceiver cb)
throws RemoteException {
+ updateCallingPackage();
mSessionCb.sendCommand(command, args, cb);
}
@Override
public boolean sendMediaButton(KeyEvent mediaButtonIntent) {
+ updateCallingPackage();
return mSessionCb.sendMediaButton(mediaButtonIntent, 0, null);
}
@@ -1111,6 +1152,7 @@
@Override
public void adjustVolume(int direction, int flags, String packageName) {
+ updateCallingPackage();
int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
@@ -1122,6 +1164,7 @@
@Override
public void setVolumeTo(int value, int flags, String packageName) {
+ updateCallingPackage();
int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
@@ -1133,94 +1176,111 @@
@Override
public void prepare() throws RemoteException {
+ updateCallingPackage();
mSessionCb.prepare();
}
@Override
public void prepareFromMediaId(String mediaId, Bundle extras)
throws RemoteException {
+ updateCallingPackage();
mSessionCb.prepareFromMediaId(mediaId, extras);
}
@Override
public void prepareFromSearch(String query, Bundle extras) throws RemoteException {
+ updateCallingPackage();
mSessionCb.prepareFromSearch(query, extras);
}
@Override
public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException {
+ updateCallingPackage();
mSessionCb.prepareFromUri(uri, extras);
}
@Override
public void play() throws RemoteException {
+ updateCallingPackage();
mSessionCb.play();
}
@Override
public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
+ updateCallingPackage();
mSessionCb.playFromMediaId(mediaId, extras);
}
@Override
public void playFromSearch(String query, Bundle extras) throws RemoteException {
+ updateCallingPackage();
mSessionCb.playFromSearch(query, extras);
}
@Override
public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
+ updateCallingPackage();
mSessionCb.playFromUri(uri, extras);
}
@Override
public void skipToQueueItem(long id) {
+ updateCallingPackage();
mSessionCb.skipToTrack(id);
}
-
@Override
public void pause() throws RemoteException {
+ updateCallingPackage();
mSessionCb.pause();
}
@Override
public void stop() throws RemoteException {
+ updateCallingPackage();
mSessionCb.stop();
}
@Override
public void next() throws RemoteException {
+ updateCallingPackage();
mSessionCb.next();
}
@Override
public void previous() throws RemoteException {
+ updateCallingPackage();
mSessionCb.previous();
}
@Override
public void fastForward() throws RemoteException {
+ updateCallingPackage();
mSessionCb.fastForward();
}
@Override
public void rewind() throws RemoteException {
+ updateCallingPackage();
mSessionCb.rewind();
}
@Override
public void seekTo(long pos) throws RemoteException {
+ updateCallingPackage();
mSessionCb.seekTo(pos);
}
@Override
public void rate(Rating rating) throws RemoteException {
+ updateCallingPackage();
mSessionCb.rate(rating);
}
@Override
public void sendCustomAction(String action, Bundle args)
throws RemoteException {
+ updateCallingPackage();
mSessionCb.sendCustomAction(action, args);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 745f476..e3c540a5 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -946,7 +946,8 @@
// won't release it later
session.sendMediaButton(keyEvent,
needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
- mKeyEventReceiver);
+ mKeyEventReceiver, getContext().getApplicationInfo().uid,
+ getContext().getPackageName());
} else {
// Launch the last PendingIntent we had with priority
UserRecord user = mUserRecords.get(mCurrentUserId);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9b70802..3f06c78 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -10668,14 +10668,14 @@
pkgSetting.setInstalled(true, userId);
pkgSetting.setHidden(false, userId);
mSettings.writePackageRestrictionsLPr(userId);
- if (pkgSetting.pkg != null) {
- prepareAppDataAfterInstall(pkgSetting.pkg);
- }
installed = true;
}
}
if (installed) {
+ if (pkgSetting.pkg != null) {
+ prepareAppDataAfterInstall(pkgSetting.pkg);
+ }
sendPackageAddedForUser(packageName, pkgSetting, userId);
}
} finally {
@@ -10726,7 +10726,7 @@
}
appId = pkgSetting.appId;
if (pkgSetting.getSuspended(userId) != suspended) {
- if (!canSuspendPackageForUser(packageName, userId)) {
+ if (!canSuspendPackageForUserLocked(packageName, userId)) {
unactionedPackages.add(packageName);
continue;
}
@@ -10765,7 +10765,7 @@
}
// TODO: investigate and add more restrictions for suspending crucial packages.
- private boolean canSuspendPackageForUser(String packageName, int userId) {
+ private boolean canSuspendPackageForUserLocked(String packageName, int userId) {
if (isPackageDeviceAdmin(packageName, userId)) {
Slog.w(TAG, "Not suspending/un-suspending package \"" + packageName
+ "\": has active device admin");
@@ -10779,6 +10779,13 @@
return false;
}
+ final PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg != null && isPrivilegedApp(pkg)) {
+ Slog.w(TAG, "Not suspending/un-suspending package \"" + packageName
+ + "\" because it is a privileged app");
+ return false;
+ }
+
return true;
}
@@ -18040,6 +18047,8 @@
* correct for all installed apps. If there is an ownership mismatch, it
* will try recovering system apps by wiping data; third-party app data is
* left intact.
+ * <p>
+ * <em>Note: To avoid a deadlock, do not call this method with {@code mPackages} lock held</em>
*/
private void prepareAppDataAfterInstall(PackageParser.Package pkg) {
prepareAppDataAfterInstallInternal(pkg);
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index bc5d73f..9c770e1 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -125,16 +125,12 @@
updateFallbackState(context, intent);
- // TODO(gsennton) for now don't update WebView on PACKAGE_CHANGED as this will
- // change the current behaviour even more, instead do this in a follow-up.
- if (intent.getAction().equals(Intent.ACTION_PACKAGE_CHANGED)) return;
-
for (WebViewProviderInfo provider : WebViewFactory.getWebViewPackages()) {
String webviewPackage = "package:" + provider.packageName;
if (webviewPackage.equals(intent.getDataString())) {
boolean updateWebView = false;
- boolean removedOldPackage = false;
+ boolean removedOrChangedOldPackage = false;
String oldProviderName = null;
PackageInfo newPackage = null;
synchronized(WebViewUpdateService.this) {
@@ -152,7 +148,7 @@
|| mCurrentWebViewPackage == null;
// We removed the old package if we received an intent to remove
// or replace the old package.
- removedOldPackage =
+ removedOrChangedOldPackage =
provider.packageName.equals(oldProviderName);
if (updateWebView) {
onWebViewProviderChanged(newPackage);
@@ -162,7 +158,8 @@
"relro with " + e);
}
}
- if(updateWebView && !removedOldPackage && oldProviderName != null) {
+ if(updateWebView && !removedOrChangedOldPackage
+ && oldProviderName != null) {
// If the provider change is the result of adding or replacing a
// package that was not the previous provider then we must kill
// packages dependent on the old package ourselves. The framework
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 12c62bd..f9e258d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -21,6 +21,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -159,6 +160,25 @@
}
}
+ void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
+ firstWindowDrawn = true;
+
+ // We now have a good window to show, remove dead placeholders
+ removeAllDeadWindows();
+
+ if (startingData != null) {
+ if (DEBUG_STARTING_WINDOW || DEBUG_ANIM) Slog.v(TAG, "Finish starting "
+ + win.mToken + ": first real window is shown, no animation");
+ // If this initial window is animating, stop it -- we will do an animation to reveal
+ // it from behind the starting window, so there is no need for it to also be doing its
+ // own stuff.
+ winAnimator.clearAnimation();
+ winAnimator.mService.mFinishedStarting.add(this);
+ winAnimator.mService.mH.sendEmptyMessage(H.FINISHED_STARTING);
+ }
+ updateReportedVisibilityLocked();
+ }
+
void updateReportedVisibilityLocked() {
if (appToken == null) {
return;
@@ -357,6 +377,9 @@
void notifyAppStopped() {
mAppStopped = true;
destroySurfaces();
+
+ // Remove any starting window that was added for this app if they are still around.
+ mTask.mService.scheduleRemoveStartingWindowLocked(this);
}
/**
@@ -594,6 +617,9 @@
if (paused) {
pw.print(prefix); pw.print("paused="); pw.println(paused);
}
+ if (mAppStopped) {
+ pw.print(prefix); pw.print("mAppStopped="); pw.println(mAppStopped);
+ }
if (numInterestingWindows != 0 || numDrawnWindows != 0
|| allDrawn || mAppAnimator.allDrawn) {
pw.print(prefix); pw.print("numInterestingWindows=");
@@ -619,7 +645,7 @@
pw.print(prefix); pw.print("startingWindow="); pw.print(startingWindow);
pw.print(" startingView="); pw.print(startingView);
pw.print(" startingDisplayed="); pw.print(startingDisplayed);
- pw.print(" startingMoved"); pw.println(startingMoved);
+ pw.print(" startingMoved="); pw.println(startingMoved);
}
if (!mFrozenBounds.isEmpty()) {
pw.print(prefix); pw.print("mFrozenBounds="); pw.print(mFrozenBounds);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 44ed7e2..40b6b50 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2347,7 +2347,8 @@
pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface);
pw.print(" mShownPosition="); mShownPosition.printShortString(pw);
pw.print(" isReadyForDisplay()="); pw.print(isReadyForDisplay());
- pw.print(" hasSavedSurface()="); pw.println(hasSavedSurface());
+ pw.print(" hasSavedSurface()="); pw.print(hasSavedSurface());
+ pw.print(" mWindowRemovalAllowed="); pw.println(mWindowRemovalAllowed);
if (dumpAll) {
pw.print(prefix); pw.print("mFrame="); mFrame.printShortString(pw);
pw.print(" last="); mLastFrame.printShortString(pw);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 2d8a4c9..c623047 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1522,23 +1522,7 @@
}
if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING && mWin.mAppToken != null) {
- mWin.mAppToken.firstWindowDrawn = true;
-
- // We now have a good window to show, remove dead placeholders
- mWin.mAppToken.removeAllDeadWindows();
-
- if (mWin.mAppToken.startingData != null) {
- if (DEBUG_STARTING_WINDOW || DEBUG_ANIM) Slog.v(TAG, "Finish starting "
- + mWin.mToken + ": first real window is shown, no animation");
- // If this initial window is animating, stop it -- we
- // will do an animation to reveal it from behind the
- // starting window, so there is no need for it to also
- // be doing its own stuff.
- clearAnimation();
- mService.mFinishedStarting.add(mWin.mAppToken);
- mService.mH.sendEmptyMessage(H.FINISHED_STARTING);
- }
- mWin.mAppToken.updateReportedVisibilityLocked();
+ mWin.mAppToken.onFirstWindowDrawn(mWin, this);
}
return true;
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 0db6f3a..2972a24 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -62,7 +62,9 @@
// However, we need to somehow handle the situation where the cropping would completely hide
// the window. We achieve this by explicitly hiding the surface and not letting it be shown.
private boolean mHiddenForCrop = false;
- private boolean mHiddenForOtherReasons = false;
+
+ // Initially a surface is hidden after just being created.
+ private boolean mHiddenForOtherReasons = true;
private final String title;
public WindowSurfaceController(SurfaceSession s,
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index d08f68a..0ece6aa 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1279,10 +1279,12 @@
}
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
- Slog.i(TAG, "WebViewFactory preparation");
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "WebViewFactoryPreparation");
- mWebViewUpdateService.prepareWebViewInSystemServer();
- Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ if (!mOnlyCore) {
+ Slog.i(TAG, "WebViewFactory preparation");
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "WebViewFactoryPreparation");
+ mWebViewUpdateService.prepareWebViewInSystemServer();
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartSystemUI");
try {
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 56d9491..5b62e03 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1053,6 +1053,16 @@
}
/**
+ * Returns all the active {@code Conference}s for which this {@code ConnectionService}
+ * has taken responsibility.
+ *
+ * @return A collection of {@code Conference}s created by this {@code ConnectionService}.
+ */
+ public final Collection<Conference> getAllConferences() {
+ return mConferenceById.values();
+ }
+
+ /**
* Create a {@code Connection} given an incoming request. This is used to attach to existing
* incoming calls.
*
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 3a7faf6..2eef7ee 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -92,8 +92,8 @@
/**
* Creates a new DisconnectCause.
*
- * @param label The localized label to show to the user to explain the disconnect.
* @param code The code for the disconnect cause.
+ * @param label The localized label to show to the user to explain the disconnect.
* @param description The localized description to show to the user to explain the disconnect.
* @param reason The reason for the disconnect.
*/
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 3ad7d34..1278c07 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -733,9 +733,28 @@
* @param subId the subscription ID, normally obtained from {@link SubscriptionManager}.
* @return A {@link PersistableBundle} containing the config for the given subId, or default
* values for an invalid subId.
+ *
+ * @deprecated use getConfig.
*/
@Nullable
public PersistableBundle getConfigForSubId(int subId) {
+ return getConfig(subId);
+ }
+
+ /**
+ * Gets the configuration values for a particular subscription, which is associated with a
+ * specific SIM card. If an invalid subId is used, the returned config will contain default
+ * values.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ *
+ * @param subId the subscription ID, normally obtained from {@link SubscriptionManager}.
+ * @return A {@link PersistableBundle} containing the config for the given subId, or default
+ * values for an invalid subId.
+ */
+ @Nullable
+ public PersistableBundle getConfig(int subId) {
try {
ICarrierConfigLoader loader = getICarrierConfigLoader();
if (loader == null) {
@@ -757,11 +776,32 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
*
- * @see #getConfigForSubId
+ * @return A {@link PersistableBundle} containing the config for the default subscription.
*/
@Nullable
public PersistableBundle getConfig() {
- return getConfigForSubId(SubscriptionManager.getDefaultSubscriptionId());
+ return getConfig(SubscriptionManager.getDefaultSubscriptionId());
+ }
+
+ /**
+ * Calling this method triggers telephony services to fetch the current carrier configuration.
+ * <p>
+ * Normally this does not need to be called because the platform reloads config on its own.
+ * This should be called by a carrier service app if it wants to update config at an arbitrary
+ * moment.
+ * </p>
+ * <p>Requires that the calling app has carrier privileges.
+ * @see #hasCarrierPrivileges
+ * <p>
+ * This method returns before the reload has completed, and
+ * {@link android.service.carrier.CarrierService#onLoadConfig} will be called from an
+ * arbitrary thread.
+ * </p>
+ *
+ * @deprecated use notifyConfigChanged.
+ */
+ public void notifyConfigChangedForSubId(int subId) {
+ notifyConfigChanged(subId);
}
/**
@@ -779,7 +819,7 @@
* arbitrary thread.
* </p>
*/
- public void notifyConfigChangedForSubId(int subId) {
+ public void notifyConfigChanged(int subId) {
try {
ICarrierConfigLoader loader = getICarrierConfigLoader();
if (loader == null) {
diff --git a/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl b/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl
index 69259d0..04cb1f2 100644
--- a/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl
+++ b/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl
@@ -18,6 +18,8 @@
import com.android.ims.ImsReasonInfo;
+import android.net.Uri;
+
/**
* A listener type for receiving notifications about the changes to
* the IMS connection(registration).
@@ -100,4 +102,9 @@
* @param count The number of waiting voice messages.
*/
void voiceMessageCountUpdate(int count);
+
+ /**
+ * Notifies the application when the list of URIs associated with IMS client is updated.
+ */
+ void registrationAssociatedUriChanged(in Uri[] uris);
}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index e87c7d40..f979c84 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -308,29 +308,13 @@
}
added = true;
- String16 attr16("attr");
-
- if (outTable->hasBagOrEntry(myPackage, attr16, ident)) {
+ if (!outTable->makeAttribute(myPackage, ident, sourcePos, type, comment, appendComment)) {
sourcePos.error("Attribute \"%s\" has already been defined\n",
String8(ident).string());
hasErrors = true;
return UNKNOWN_ERROR;
}
-
- char numberStr[16];
- sprintf(numberStr, "%d", type);
- status_t err = outTable->addBag(sourcePos, myPackage,
- attr16, ident, String16(""),
- String16("^type"),
- String16(numberStr), NULL, NULL);
- if (err != NO_ERROR) {
- hasErrors = true;
- return err;
- }
- outTable->appendComment(myPackage, attr16, ident, comment, appendComment);
- //printf("Attribute %s comment: %s\n", String8(ident).string(),
- // String8(comment).string());
- return err;
+ return UNKNOWN_ERROR;
}
};
@@ -2115,6 +2099,50 @@
return false;
}
+bool ResourceTable::makeAttribute(const String16& package,
+ const String16& name,
+ const SourcePos& source,
+ int32_t format,
+ const String16& comment,
+ bool shouldAppendComment) {
+ const String16 attr16("attr");
+
+ // First look for this in the included resources...
+ uint32_t rid = mAssets->getIncludedResources()
+ .identifierForName(name.string(), name.size(),
+ attr16.string(), attr16.size(),
+ package.string(), package.size());
+ if (rid != 0) {
+ return false;
+ }
+
+ sp<ResourceTable::Entry> entry = getEntry(package, attr16, name, source, false);
+ if (entry == NULL) {
+ return false;
+ }
+
+ if (entry->makeItABag(source) != NO_ERROR) {
+ return false;
+ }
+
+ const String16 formatKey16("^type");
+ const String16 formatValue16(String8::format("%d", format));
+
+ ssize_t idx = entry->getBag().indexOfKey(formatKey16);
+ if (idx >= 0) {
+ // We have already set a format for this attribute, check if they are different.
+ // We allow duplicate attribute definitions so long as they are identical.
+ // This is to ensure interoperation with libraries that define the same generic attribute.
+ if (entry->getBag().valueAt(idx).value != formatValue16) {
+ return false;
+ }
+ } else {
+ entry->addToBag(source, formatKey16, formatValue16);
+ }
+ appendComment(package, attr16, name, comment, shouldAppendComment);
+ return true;
+}
+
void ResourceTable::canAddEntry(const SourcePos& pos,
const String16& package, const String16& type, const String16& name)
{
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index 4b7b3cd..cf1e992 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -571,6 +571,18 @@
void getDensityVaryingResources(KeyedVector<Symbol, Vector<SymbolDefinition> >& resources);
+ /**
+ * Make an attribute with the specified format. If another attribute with the same name but
+ * different format exists, this method returns false. If the name is not taken, or if the
+ * format is identical, this returns true.
+ */
+ bool makeAttribute(const String16& package,
+ const String16& name,
+ const SourcePos& source,
+ int32_t format,
+ const String16& comment,
+ bool appendComment);
+
private:
void writePublicDefinitions(const String16& package, FILE* fp, bool pub);
sp<Package> getPackage(const String16& package);
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index ed12bdf..a46aaec 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -160,21 +160,6 @@
}
}
- /** @hide */
- public static final int ENABLED = 0;
- /** @hide */
- public static final int AUTO_ROAM_DISABLED = 16;
- /** @hide */
- public static final int AUTO_JOIN_DISABLED = 32;
- /** @hide */
- public static final int AUTHENTICATION_ERROR = 128;
-
- /**
- * Status: indicating join status
- * @hide
- */
- public int autoJoinStatus;
-
/**
* num IP configuration failures
* @hide
@@ -187,17 +172,6 @@
*/
public long blackListTimestamp;
- /** @hide **/
- public void setAutoJoinStatus(int status) {
- if (status < 0) status = 0;
- if (status == 0) {
- blackListTimestamp = 0;
- } else if (status > autoJoinStatus) {
- blackListTimestamp = System.currentTimeMillis();
- }
- autoJoinStatus = status;
- }
-
/**
* Status: indicating the scan result is not a result
* that is part of user's saved configurations
@@ -462,7 +436,6 @@
distanceCm = source.distanceCm;
distanceSdCm = source.distanceSdCm;
seen = source.seen;
- autoJoinStatus = source.autoJoinStatus;
untrusted = source.untrusted;
numConnection = source.numConnection;
numUsage = source.numUsage;
@@ -506,9 +479,6 @@
sb.append(", passpoint: ");
sb.append(((flags & FLAG_PASSPOINT_NETWORK) != 0) ? "yes" : "no");
- if (autoJoinStatus != 0) {
- sb.append(", status: ").append(autoJoinStatus);
- }
sb.append(", ChannelBandwidth: ").append(channelWidth);
sb.append(", centerFreq0: ").append(centerFreq0);
sb.append(", centerFreq1: ").append(centerFreq1);
@@ -544,7 +514,6 @@
dest.writeInt(centerFreq0);
dest.writeInt(centerFreq1);
dest.writeLong(seen);
- dest.writeInt(autoJoinStatus);
dest.writeInt(untrusted ? 1 : 0);
dest.writeInt(numConnection);
dest.writeInt(numUsage);
@@ -615,7 +584,6 @@
);
sr.seen = in.readLong();
- sr.autoJoinStatus = in.readInt();
sr.untrusted = in.readInt() != 0;
sr.numConnection = in.readInt();
sr.numUsage = in.readInt();